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 Active Directory secret engine support #902

Merged
merged 14 commits into from
Nov 19, 2020
Merged

Add Active Directory secret engine support #902

merged 14 commits into from
Nov 19, 2020

Conversation

jasonodonnell
Copy link
Collaborator

@jasonodonnell jasonodonnell commented Nov 10, 2020

This PR adds support for the Active Directory secret engine. The following endpoints are supported.

  • "/ad/config" as a resource
  • "/ad/roles/<role name>" as a resource
  • "/ad/library/<set name>" as a resource
  • "/ad/creds/<role name>" as a data source

Notably the rotate-root and check-in/check-out functionality aren't supported. These aren't really managed resources and don't work well with TF.

Currently automated acceptance testing isn't integrated with CircleCI because the Samba Docker image I'm using requires privileged, which is not possible with the docker-compose CircleCI service. This needs to be changed to use a machine instead so we can set the privileged flag. Integrated automated testing will be a follow up PR.

To test this, you can run the following:

export VAULT_DEV_ROOT_TOKEN_ID="root"
export VAULT_TOKEN="root"
export VAULT_ADDR="http://0.0.0.0:8200"

export AD_BINDDN="CN=Administrator,CN=Users,DC=corp,DC=example,DC=net"
export AD_BINDPASS="SuperSecretPassw0rd"
export AD_URL="ldaps://ad"

docker network create --driver bridge vault

docker run \
  --name=vault \
  --hostname=vault \
  --network=vault \
  -p 8200:8200 \
  -e VAULT_DEV_ROOT_TOKEN_ID="root" \
  -e VAULT_ADDR="http://localhost:8200" \
  -e VAULT_DEV_LISTEN_ADDRESS="0.0.0.0:8200" \
  --privileged \
  --detach vault:latest

docker run \
  --name=ad \
  --hostname=ad \
  --network=vault \
  --privileged \
  -p 389:389 \
  -e SAMBA_DC_REALM="corp.example.net" \
  -e SAMBA_DC_DOMAIN="EXAMPLE" \
  -e SAMBA_DC_ADMIN_PASSWD="SuperSecretPassw0rd" \
  -e SAMBA_DC_DNS_BACKEND="SAMBA_INTERNAL" \
  --detach "laslabs/alpine-samba-dc" samba

sleep 30

cat <<'EOF' >> user.ldif
dn: CN=Bob,CN=Users,DC=corp,DC=example,DC=net
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: Bob
description: test account
name: Bob
sAMAccountName: Bob
distinguishedName: CN=Bob,CN=Users,DC=corp,DC=example,DC=net
userPrincipalName: Bob

dn: CN=Mary,CN=Users,DC=corp,DC=example,DC=net
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: Mary
description: test account
name: Mary
sAMAccountName: Mary
distinguishedName: CN=Mary,CN=Users,DC=corp,DC=example,DC=net
userPrincipalName: Mary
EOF

LDAPTLS_REQCERT=never ldapadd -h 127.0.0.1 -Z -p 389 -w "SuperSecretPassw0rd" -D "CN=Administrator,CN=Users,DC=corp,DC=example,DC=net" -f user.ldif

make testacc TESTARGS='-run TestAccDataSourceADAccessCredentials*'
make testacc TESTARGS='-run TestAccADSecretBackend*'

If you want to test this via terraform binary, here's an example resource:

provider "vault" {
  address = "http://0.0.0.0:8200"
  token = "root"
}

resource "vault_ad_secret_backend" "config" {
    backend = "ad"
    binddn = "CN=Administrator,CN=Users,DC=corp,DC=example,DC=net"
    bindpass = "SuperSecretPassw0rd"
    url = "ldaps://ad"
    insecure_tls = "true"
    userdn = "CN=Users,DC=corp,DC=example,DC=net"
}

resource "vault_ad_secret_role" "bob" {
    backend = vault_ad_secret_backend.config.backend
    role = "bob"
    service_account_name = "Bob"
    ttl = 60
}

data "vault_ad_access_credentials" "bob_creds" {
  backend = vault_ad_secret_backend.config.backend
  role    = vault_ad_secret_role.bob.role
  depends_on = [vault_ad_secret_role.bob]
}

resource "vault_ad_secret_role" "mary" {
    backend = vault_ad_secret_backend.config.backend
    role = "mary"
    service_account_name = "Mary"
    ttl = 60
}

resource "vault_ad_secret_library" "qa" {
    backend = vault_ad_secret_backend.config.backend
    name = "qa"
    service_account_names = ["Bob", "Mary"]
    ttl = 60
    max_ttl = 120
    disable_check_in_enforcement = true
}

data "vault_ad_access_credentials" "mary_creds" {
  backend = vault_ad_secret_backend.config.backend
  role    = vault_ad_secret_role.mary.role
  depends_on = [vault_ad_secret_role.mary]
}

output "bob_password" {
  value = data.vault_ad_access_credentials.bob_creds.current_password
}

output "mary_password" {
  value = data.vault_ad_access_credentials.mary_creds.current_password
}
Related test output
[~/Git/terraform-provider-vault] make testacc TESTARGS='-run TestAccADSecretBackend*'
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test $(go list ./... |grep -v 'vendor') -v -run TestAccADSecretBackend* -timeout 120m
?   	github.com/terraform-providers/terraform-provider-vault	[no test files]
?   	github.com/terraform-providers/terraform-provider-vault/cmd/coverage	[no test files]
?   	github.com/terraform-providers/terraform-provider-vault/cmd/generate	[no test files]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/codegen	(cached) [no tests to run]
?   	github.com/terraform-providers/terraform-provider-vault/generated	[no test files]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/datasources/transform/decode	3.462s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/datasources/transform/encode	2.830s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/alphabet	4.129s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/role	4.748s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/template	5.562s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/transformation	5.317s [no tests to run]
?   	github.com/terraform-providers/terraform-provider-vault/schema	[no test files]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/util	(cached) [no tests to run]
=== RUN   TestAccADSecretBackendRole_basic
--- PASS: TestAccADSecretBackendRole_basic (0.47s)
=== RUN   TestAccADSecretBackendRole_import
--- PASS: TestAccADSecretBackendRole_import (0.25s)
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/vault	6.046s

[~/Git/terraform-provider-vault] make testacc TESTARGS='-run TestAccDataSourceADAccessCredentials*'
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test $(go list ./... |grep -v 'vendor') -v -run TestAccDataSourceADAccessCredentials* -timeout 120m
?   	github.com/terraform-providers/terraform-provider-vault	[no test files]
?   	github.com/terraform-providers/terraform-provider-vault/cmd/coverage	[no test files]
?   	github.com/terraform-providers/terraform-provider-vault/cmd/generate	[no test files]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/codegen	0.387s [no tests to run]
?   	github.com/terraform-providers/terraform-provider-vault/generated	[no test files]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/datasources/transform/decode	0.993s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/datasources/transform/encode	2.346s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/alphabet	2.087s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/role	1.245s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/template	1.831s [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/generated/resources/transform/transformation	2.844s [no tests to run]
?   	github.com/terraform-providers/terraform-provider-vault/schema	[no test files]
testing: warning: no tests to run
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/util	0.586s [no tests to run]
=== RUN   TestAccDataSourceADAccessCredentials_basic
--- PASS: TestAccDataSourceADAccessCredentials_basic (0.45s)
PASS
ok  	github.com/terraform-providers/terraform-provider-vault/vault	3.068s

@jeffsanicola
Copy link
Contributor

Will "ad/library/<set_name>" resources also be supported in this release or is that expected in a future release?

@jasonodonnell
Copy link
Collaborator Author

jasonodonnell commented Nov 10, 2020

@jeffsanicola Not in this release but could happen in a future release. Do you see value in this feature?

See my note in the PR:

Notably the rotate-root, library and check-in/check-out functionality aren't supported. These aren't really managed resources and don't work well with TF.

@jasonodonnell
Copy link
Collaborator Author

@jeffsanicola Thinking about this more, library does make sense. I will add it to this PR.

@jeffsanicola
Copy link
Contributor

@jasonodonnell Awesome! Thank you for re-evaluating.

vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_roles.go Show resolved Hide resolved
@jasonodonnell
Copy link
Collaborator Author

@jeffsanicola ad/library support has been added.

Copy link
Contributor

@tomhjp tomhjp left a comment

Choose a reason for hiding this comment

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

LGTM! A few small suggestions

return strings.Trim(v.(string), "/")
},
},
"role": {
Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't checked against other examples in this provider, but it would feel more like idiomatic terraform config to me if this was "name".

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The use of role is precedent here. Agreed its strange but I suppose consistency is correct.

"anonymous_group_search": {
Type: schema.TypeBool,
Optional: true,
Description: `Use anonymous binds when performing LDAP group searches (if true the initial credentials will still be used for the initial connection test).`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the assumption that when default is not specified in the description for booleans, it defaults to false?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's correct. Also remember that the backend itself has defaults, so it's not always proper for Terraform to duplicate it, I think.

vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_backend.go Show resolved Hide resolved
vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_backend.go Show resolved Hide resolved
d.SetId(backend)

data := map[string]interface{}{}
if v, ok := d.GetOkExists("anonymous_group_search"); ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to use a loop over a slice of strings here?

Definitely not for this PR, but a wider point:
Also I know it's consistent with the style elsewhere, but it scares me that we duplicate all these strings between the schema and implementation. It seems like an easy win to just have some consts, although given everything is currently in the vault package, perhaps we would want to split into multiple packages to prevent massive top-level name noise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this would make a good cleanup across the board. Something we could target in the future.

vault/resource_ad_secret_backend.go Show resolved Hide resolved
vault/resource_ad_secret_library.go Show resolved Hide resolved
if err != nil {
return fmt.Errorf("error reading %q: %s", rolePath, err)
}
log.Printf("[DEBUG] Read %q", rolePath)
Copy link
Contributor

Choose a reason for hiding this comment

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

Another comment for the wider code base rather than this PR - it would be lovely to get some helper functions for debug/warn/error etc logs

Copy link
Member

@catsby catsby left a comment

Choose a reason for hiding this comment

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

There are some minor things that I think need changing, and otherwise some questions I had or some nitpicks.

I notice that there are a handful of generated resource documents that were moved? I believe those were moved in #871 and should stay where they are. The Terraform registry now uses file structure as a means of document structure, and expects docs to be in specific locations. Let me know if you have questions or need help resolving those

Comment on lines 349 to 352
mountResp, err := client.Sys().MountConfig(path)
if err != nil {
return fmt.Errorf("error reading %q: %s", path, err)
}
Copy link
Member

Choose a reason for hiding this comment

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

If the backend is removed out of band, this will fail with a 404 and we'll be stuck. If we get a 404 type error here we need to remove from state like we do on line 369 below

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

vault/resource_ad_secret_backend.go Show resolved Hide resolved
vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_library.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_library.go Show resolved Hide resolved
vault/resource_ad_secret_library.go Show resolved Hide resolved
vault/resource_ad_secret_library.go Show resolved Hide resolved
vault/resource_ad_secret_roles.go Show resolved Hide resolved
<li<%= sidebar_current("docs-vault-datasource-ad-access-credentials") %>>
<a href="/docs/providers/vault/d/ad_access_credentials.html">vault_ad_access_credentials</a>
</li>

Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately this file is no longer used at all, and should be removed in a separate PR

@calvn
Copy link
Member

calvn commented Nov 16, 2020

By default, check-in must be called by the same entity or client token used for check-out.

Does TF use the same client token across runs? If not, this could be problematic for the library flow if a set was checked out by one run but then later checked in by another apply. @catsby do you see any potential issues with this?

@jasonodonnell
Copy link
Collaborator Author

@calvn This provider does not support checkin/checkout functionality. We only allow you to create the library sets that can be used by checkin/checkout workflows.

@calvn
Copy link
Member

calvn commented Nov 17, 2020

Ah, we're good then!

@jasonodonnell
Copy link
Collaborator Author

@catsby Good call on the transform docs being moved. Not sure how that happened but I fixed it.

vault/resource_ad_secret_library.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
vault/resource_ad_secret_backend.go Outdated Show resolved Hide resolved
Copy link
Member

@catsby catsby left a comment

Choose a reason for hiding this comment

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

👍

jasonodonnell and others added 4 commits November 17, 2020 11:30
Co-authored-by: Theron Voran <tvoran@users.noreply.github.com>
Co-authored-by: Theron Voran <tvoran@users.noreply.github.com>
@jasonodonnell jasonodonnell merged commit e97b888 into master Nov 19, 2020
dandandy pushed a commit to dandandy/terraform-provider-vault that referenced this pull request Jun 17, 2021
* Add Active Directory secret engine support

* Add optional to backend

* Update vault/resource_ad_secret_roles.go

Co-authored-by: Theron Voran <tvoran@users.noreply.github.com>

* Add deprecated flag for length and formatter

* Add library support

* Update documentation

* Update vault/resource_ad_secret_backend.go

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>

* Remove optional from description

* Fix typo in library doc

* Move documentation back

* Update vault/resource_ad_secret_backend.go

Co-authored-by: Theron Voran <tvoran@users.noreply.github.com>

* Update vault/resource_ad_secret_backend.go

Co-authored-by: Theron Voran <tvoran@users.noreply.github.com>

* Update ttl description

* Add note about seconds to ttl

Co-authored-by: Theron Voran <tvoran@users.noreply.github.com>
Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants