Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for LDAP and SAML groups #314

Merged
merged 25 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
Active Directory Federations Services (ADFS) as IdP using WS-TRUST auth endpoint
"/adfs/services/trust/13/usernamemixed"
[#304](https://github.com/vmware/go-vcloud-director/pull/304)

* Add support for group management using `CreateGroup`, `GetGroupByHref`, `GetGroupById`,
`GetGroupByName`, `GetGroupByNameOrId`, `Delete`, `Update`, `NewGroup` functions [#314](https://github.com/vmware/go-vcloud-director/pull/314)
* Add LDAP administration functions for Org `LdapConfigure` and `LdapDisable` [#314](https://github.com/vmware/go-vcloud-director/pull/314)

## 2.7.0 (April 10,2020)

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ default: fmtcheck vet static build
# test runs the test suite and vets the code
test: testunit
@echo "==> Running Functional Tests"
cd govcd && go test -tags "functional" -timeout=200m -check.vv .
cd govcd && go test -tags "functional" -timeout=300m -check.vv .
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please tell more why are you adding additional one hundred minutes to the timeout :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not related to this exact group PR. I did hit timeouts in general for full suite runs in slower envs that is why I increased the "default".


# testunit runs the unit tests
testunit: fmtcheck
Expand Down
37 changes: 37 additions & 0 deletions govcd/adminorg_administration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"net/http"

"github.com/vmware/go-vcloud-director/v2/types/v56"
"github.com/vmware/go-vcloud-director/v2/util"
)

// LdapConfigure allows to configure LDAP mode in use by the Org
func (adminOrg *AdminOrg) LdapConfigure(settings *types.OrgLdapSettingsType) error {
vbauzys marked this conversation as resolved.
Show resolved Hide resolved
util.Logger.Printf("[DEBUG] Configuring LDAP mode for Org name %s", adminOrg.AdminOrg.Name)

// Xmlns field is not mandatory when `types.OrgLdapSettingsType` is set as part of whole
// `AdminOrg` structure but it must be set when directly updating LDAP. For that reason
// `types.OrgLdapSettingsType` Xmlns struct tag has 'omitempty' set
settings.Xmlns = types.XMLNamespaceVCloud

href := adminOrg.AdminOrg.HREF + "/settings/ldap"
_, err := adminOrg.client.ExecuteRequest(href, http.MethodPut, types.MimeOrgLdapSettings,
"error updating LDAP settings: %s", settings, nil)
if err != nil {
return fmt.Errorf("error updating LDAP mode for Org name '%s': %s", adminOrg.AdminOrg.Name, err)
}

return nil
}

// LdapDisable wraps LdapConfigure to disable LDAP configuration for org
func (adminOrg *AdminOrg) LdapDisable() error {
vbauzys marked this conversation as resolved.
Show resolved Hide resolved
return adminOrg.LdapConfigure(&types.OrgLdapSettingsType{OrgLdapMode: types.LdapModeNone})
}
313 changes: 313 additions & 0 deletions govcd/adminorg_ldap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
// +build user functional ALL

/*
* Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"time"

"github.com/vmware/go-vcloud-director/v2/types/v56"
. "gopkg.in/check.v1"
)

// Test_LDAP serves as a "subtest" framework for tests requiring LDAP configuration. It sets up LDAP
// server and configuration for Org and cleans up this test run.
//
// Prerequisites:
// * External network subnet must have access to internet
// * Correct DNS servers must be set for external network so that guest VM can resolve DNS records
func (vcd *TestVCD) Test_LDAP(check *C) {
fmt.Println("Setting up LDAP")
networkName, vappName, vmName := vcd.configureLdap(check)
defer func() {
fmt.Println("Unconfiguring LDAP")
vcd.unconfigureLdap(check, networkName, vappName, vmName)
}()

// Run tests requiring LDAP from here.
vcd.test_GroupCRUD(check)
vcd.test_GroupFinderGetGenericEntity(check)

}

// configureLdap creates direct network, spawns Photon OS VM with LDAP server and configures vCD to
// use LDAP server
func (vcd *TestVCD) configureLdap(check *C) (string, string, string) {
if vcd.skipAdminTests {
check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName()))
vbauzys marked this conversation as resolved.
Show resolved Hide resolved
}

// Create direct network to expose LDAP server on external network
directNetworkName := createDirectNetwork(vcd, check)

// Launch LDAP server on external network
ldapHostIp, vappName, vmName := createLdapServer(vcd, check, directNetworkName)

// Configure vCD to use new LDAP server
orgConfigureLdap(vcd, check, ldapHostIp)
vbauzys marked this conversation as resolved.
Show resolved Hide resolved

return directNetworkName, vappName, vmName
}

// unconfigureLdap cleans up LDAP configuration created by `configureLdap` immediately to reduce
// resource usage
func (vcd *TestVCD) unconfigureLdap(check *C, networkName, vAppName, vmName string) {
if vcd.skipAdminTests {
check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName()))
}

// Get Org Vdc
org, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org)
check.Assert(err, IsNil)
vdc, err := org.GetVDCByName(vcd.config.VCD.Vdc, false)
check.Assert(err, IsNil)
check.Assert(vdc, NotNil)

vapp, err := vdc.GetVAppByName(vAppName, false)
check.Assert(err, IsNil)

vm, err := vapp.GetVMByName(vmName, false)
check.Assert(err, IsNil)

// Remove VM
task, err := vm.Undeploy()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)

err = vapp.RemoveVM(*vm)
check.Assert(err, IsNil)

// undeploy and remove vApp
task, err = vapp.Undeploy()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)

task, err = vapp.Delete()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)

// Remove network
err = RemoveOrgVdcNetworkIfExists(*vcd.vdc, networkName)
check.Assert(err, IsNil)

// Clear LDAP configuration
err = org.LdapDisable()
check.Assert(err, IsNil)

}

// orgConfigureLdap sets up LDAP configuration in vCD org specified by vcd.config.VCD.Org variable
func orgConfigureLdap(vcd *TestVCD, check *C, ldapHostIp string) {
fmt.Printf("# Configuring LDAP settings for Org '%s'", vcd.config.VCD.Org)

org, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org)
check.Assert(err, IsNil)
// The below settings are tailored for LDAP docker testing image
// https://github.com/rroemhild/docker-test-openldap
ldapSettings := &types.OrgLdapSettingsType{
OrgLdapMode: types.LdapModeCustom,
CustomOrgLdapSettings: &types.CustomOrgLdapSettings{
HostName: ldapHostIp,
Port: 389,
SearchBase: "dc=planetexpress,dc=com",
AuthenticationMechanism: "SIMPLE",
ConnectorType: "OPEN_LDAP",
Username: "cn=admin,dc=planetexpress,dc=com",
Password: "GoodNewsEveryone",
UserAttributes: &types.OrgLdapUserAttributes{
ObjectClass: "user",
ObjectIdentifier: "objectGuid",
Username: "sAMAccountName",
Email: "mail",
FullName: "cn",
GivenName: "givenName",
Surname: "sn",
Telephone: "telephone",
GroupMembershipIdentifier: "dn",
GroupBackLinkIdentifier: "tokenGroups",
},
GroupAttributes: &types.OrgLdapGroupAttributes{
ObjectClass: "group",
ObjectIdentifier: "objectGuid",
GroupName: "cn",
Membership: "member",
MembershipIdentifier: "dn",
},
},
}

err = org.LdapConfigure(ldapSettings)
check.Assert(err, IsNil)

fmt.Println(" Done")
AddToCleanupList("LDAP-configuration", "orgLdapSettings", org.AdminOrg.Name, check.TestName())
}

// createLdapServer spawns a vApp and photon OS VM. Using customization script it starts a testing
// LDAP server in docker container which has a few users and groups defined.
// In essence it creates two groups - "admin_staff" and "ship_crew" and a few users.
// More information about users and groups in: https://github.com/rroemhild/docker-test-openldap
func createLdapServer(vcd *TestVCD, check *C, directNetworkName string) (string, string, string) {
vAppName := "ldap"
// The customization script waits until IP address is set on the NIC because Guest tools run
// script and network configuration together. If the script runs too quick - there is a risk
// that network card is not yet configured and it will not be able to pull docker image from
// remote. Guest tools could also be interrupted if the script below failed before NICs are
// configured therefore it is run in background.
// It waits until "inet" (not "inet6") is set and then runs docker container
const ldapCustomizationScript = `
{
until ip a show eth0 | grep "inet "
do
sleep 1
done
systemctl enable docker
systemctl start docker
docker run --name ldap-server --restart=always --privileged -d -p 389:389 rroemhild/test-openldap
} &
`
// Get Org, Vdc
org, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org)
check.Assert(err, IsNil)
vdc, err := org.GetVDCByName(vcd.config.VCD.Vdc, false)
check.Assert(err, IsNil)
check.Assert(vdc, NotNil)

// Find catalog and catalog item
catalog, err := org.GetCatalogByName(vcd.config.VCD.Catalog.Name, false)
check.Assert(err, IsNil)
check.Assert(catalog, NotNil)
catalogItem, err := catalog.GetCatalogItemByName(vcd.config.VCD.Catalog.CatalogItem, false)
check.Assert(err, IsNil)
// Skip the test if catalog item is not Photon OS
if !isItemPhotonOs(*catalogItem) {
vbauzys marked this conversation as resolved.
Show resolved Hide resolved
check.Skip(fmt.Sprintf("Skipping test because catalog item %s is not Photon OS",
vcd.config.VCD.Catalog.CatalogItem))
}
fmt.Printf("# Creating RAW vApp '%s'", vAppName)
vappTemplate, err := catalogItem.GetVAppTemplate()
check.Assert(err, IsNil)
// Compose Raw vApp
err = vdc.ComposeRawVApp(vAppName)
check.Assert(err, IsNil)
vapp, err := vdc.GetVAppByName(vAppName, true)
check.Assert(err, IsNil)
// vApp was created - adding it to cleanup list (using prepend to remove it before direct
// network removal)
PrependToCleanupList(vAppName, "vapp", "", check.TestName())
// Wait until vApp becomes configurable
initialVappStatus, err := vapp.GetStatus()
check.Assert(err, IsNil)
if initialVappStatus != "RESOLVED" { // RESOLVED vApp is ready to accept operations
err = vapp.BlockWhileStatus(initialVappStatus, vapp.client.MaxRetryTimeout)
check.Assert(err, IsNil)
}
fmt.Printf(". Done\n")

// Attach VDC network to vApp so that VMs can use it
fmt.Printf("# Attaching network '%s'", directNetworkName)
net, err := vdc.GetOrgVdcNetworkByName(directNetworkName, false)
check.Assert(err, IsNil)
task, err := vapp.AddRAWNetworkConfig([]*types.OrgVDCNetwork{net.OrgVDCNetwork})
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
fmt.Printf(". Done\n")

// Create VM
desiredNetConfig := types.NetworkConnectionSection{}
desiredNetConfig.PrimaryNetworkConnectionIndex = 0
desiredNetConfig.NetworkConnection = append(desiredNetConfig.NetworkConnection,
&types.NetworkConnection{
IsConnected: true,
IPAddressAllocationMode: types.IPAllocationModePool,
Network: directNetworkName,
NetworkConnectionIndex: 0,
})

// LDAP docker container does not start if Photon OS VM does not have at least 1024 of RAM
ldapVm, err := spawnVM("ldap-vm", 1024, *vdc, *vapp, desiredNetConfig, vappTemplate, check, ldapCustomizationScript)
check.Assert(err, IsNil)

// Must be deleted before vApp to avoid IP leak
PrependToCleanupList(ldapVm.VM.Name, "vm", vAppName, check.TestName())

// Got VM - ensure that TCP port for ldap service is open and reachable
ldapHostIp := ldapVm.VM.NetworkConnectionSection.NetworkConnection[0].IPAddress
fmt.Printf("# Waiting for server %s to respond on port 389: ", ldapHostIp)
timerStart := time.Now()
isLdapServiceUp := isTcpPortOpen(ldapHostIp, "389", vapp.client.MaxRetryTimeout)
check.Assert(isLdapServiceUp, Equals, true)
fmt.Printf("# Time taken to start LDAP container: %s\n", time.Since(timerStart))

return ldapHostIp, vAppName, ldapVm.VM.Name
}

// createDirectNetwork creates a direct network attached to existing external network
func createDirectNetwork(vcd *TestVCD, check *C) string {
networkName := check.TestName()
fmt.Printf("# Creating direct network %s.", networkName)

if vcd.skipAdminTests {
check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName()))
}
err := RemoveOrgVdcNetworkIfExists(*vcd.vdc, networkName)
if err != nil {
check.Skip(fmt.Sprintf("Error deleting network : %s", err))
}

if vcd.config.VCD.ExternalNetwork == "" {
check.Skip("[" + check.TestName() + "] external network not provided")
}
externalNetwork, err := vcd.client.GetExternalNetworkByName(vcd.config.VCD.ExternalNetwork)
if err != nil {
check.Skip("[" + check.TestName() + "] parent network not found")
return ""
}
// Note that there is no IPScope for this type of network
description := "Created by govcd test"
var networkConfig = types.OrgVDCNetwork{
Xmlns: types.XMLNamespaceVCloud,
Name: networkName,
Description: description,
Configuration: &types.NetworkConfiguration{
FenceMode: types.FenceModeBridged,
ParentNetwork: &types.Reference{
HREF: externalNetwork.ExternalNetwork.HREF,
Name: externalNetwork.ExternalNetwork.Name,
Type: externalNetwork.ExternalNetwork.Type,
},
BackwardCompatibilityMode: true,
},
IsShared: false,
}
LogNetwork(networkConfig)

task, err := vcd.vdc.CreateOrgVDCNetwork(&networkConfig)
if err != nil {
fmt.Printf("error creating the network: %s", err)
}
check.Assert(err, IsNil)
if task == (Task{}) {
fmt.Printf("NULL task retrieved after network creation")
}
check.Assert(task.Task.HREF, Not(Equals), "")

AddToCleanupList(networkName,
"network", vcd.org.Org.Name+"|"+vcd.vdc.Vdc.Name, check.TestName())

err = task.WaitInspectTaskCompletion(LogTask, 10)
if err != nil {
fmt.Printf("error performing task: %s", err)
}
check.Assert(err, IsNil)
fmt.Println(" Done")
return networkName
}
Loading