Hashicorp Vault LDAP Authentication and LDAP Groups


Integrating HashiCorp Vault with an existing LDAP system such as Active Directory is a convenient way to manage user authentication and authorization. Follow along below for an example of setting this up.

Note, I am piping curl output to jq for better formatting. Check it out here.

Updated - check the updates at the bottom of the post for a briefer setup.

Enable the LDAP Auth Method

API:

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request POST http://localhost:8200/v1/sys/auth/ldap --data '{"type":"ldap","description":"Login with LDAP"}'

Confirm

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request GET http://localhost:8200/v1/sys/auth | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
{
  "token/": {
    "accessor": "auth_token_62e1836c",
    "config": {
      "default_lease_ttl": 0,
      "force_no_cache": false,
      "max_lease_ttl": 0,
      "token_type": "default-service"
    },
    "description": "token based credentials",
    "local": false,
    "options": null,
    "seal_wrap": false,
    "type": "token",
    "uuid": "19661fc1-15e2-05c6-492e-445480a3fd4a"
  },
  "ldap/": {
    "accessor": "auth_ldap_e620cbc4",
    "config": {
      "default_lease_ttl": 0,
      "force_no_cache": false,
      "max_lease_ttl": 0,
      "token_type": "default-service"
    },
    "description": "Login with LDAP",
    "local": false,
    "options": {},
    "seal_wrap": false,
    "type": "ldap",
    "uuid": "001b8deb-52f2-72b6-edbe-5a15e06c9d8d"
  },
  "request_id": "5afff2e8-5a27-02e9-cd66-c1b567b79368",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "ldap/": {
      "accessor": "auth_ldap_e620cbc4",
      "config": {
        "default_lease_ttl": 0,
        "force_no_cache": false,
        "max_lease_ttl": 0,
        "token_type": "default-service"
      },
      "description": "Login with LDAP",
      "local": false,
      "options": {},
      "seal_wrap": false,
      "type": "ldap",
      "uuid": "001b8deb-52f2-72b6-edbe-5a15e06c9d8d"
    },
    "token/": {
      "accessor": "auth_token_62e1836c",
      "config": {
        "default_lease_ttl": 0,
        "force_no_cache": false,
        "max_lease_ttl": 0,
        "token_type": "default-service"
      },
      "description": "token based credentials",
      "local": false,
      "options": null,
      "seal_wrap": false,
      "type": "token",
      "uuid": "19661fc1-15e2-05c6-492e-445480a3fd4a"
    }
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

Reference: https://www.vaultproject.io/api/system/auth.html#enable-auth-method

CLI:

1
vault auth enable ldap

Confirm:

1
2
3
4
5
6
vault auth list

Path      Type     Accessor               Description
----      ----     --------               -----------
ldap/     ldap     auth_ldap_e620cbc4     Login with LDAP
token/    token    auth_token_62e1836c    token based credentials

Reference: https://www.vaultproject.io/docs/auth/index.html#auth-methods

Configure the LDAP Auth Method

Note, there is an open issue regarding the LDAP auth method at the time of writing, which impacts group searching. To workaround the problem, do not define the upndomain attribute. Refer to the GitHub issue here: https://github.com/hashicorp/vault/issues/6325

API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request POST http://localhost:8200/v1/auth/ldap/config --data @- <<EOF
{
  "url":"ldap://ec2-3-106-146-38.ap-southeast-2.compute.amazonaws.com:389",
  "binddn":"CN=svc_vault_bind,OU=Service Accounts,DC=vaulttest,DC=internal",
  "bindpass":"pass@word1",
  "userdn":"OU=Vault Users,DC=vaulttest,DC=internal",
  "userattr":"sAMAccountName",
  "upndomain":"vaulttest.internal",
  "groupdn":"OU=Security Groups,DC=vaulttest,DC=internal"
}
EOF

Confirm:

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request GET http://localhost:8200/v1/auth/ldap/config | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
  "request_id": "ea451a0b-0de6-0a25-7bca-f01c9745fb70",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "binddn": "CN=svc_vault_bind,OU=Service Accounts,DC=vaulttest,DC=internal",
    "case_sensitive_names": false,
    "certificate": "",
    "deny_null_bind": true,
    "discoverdn": false,
    "groupattr": "cn",
    "groupdn": "OU=Security Groups,DC=vaulttest,DC=internal",
    "groupfilter": "(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))",
    "insecure_tls": false,
    "starttls": false,
    "tls_max_version": "tls12",
    "tls_min_version": "tls12",
    "token_bound_cidrs": [],
    "token_explicit_max_ttl": 0,
    "token_max_ttl": 0,
    "token_no_default_policy": false,
    "token_num_uses": 0,
    "token_period": 0,
    "token_policies": [],
    "token_ttl": 0,
    "token_type": "default",
    "upndomain": "vaulttest.internal",
    "url": "ldap://ec2-3-106-146-38.ap-southeast-2.compute.amazonaws.com:389",
    "use_pre111_group_cn_behavior": false,
    "use_token_groups": false,
    "userattr": "samaccountname",
    "userdn": "OU=Vault Users,DC=vaulttest,DC=internal"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

Reference: https://www.vaultproject.io/api/auth/ldap/index.html#configure-ldap

CLI:

1
2
3
4
5
6
7
8
./vault write auth/ldap/config \
  url="ldap://ec2-3-106-146-38.ap-southeast-2.compute.amazonaws.com:389" \
  binddn="CN=svc_vault_bind,OU=Service Accounts,DC=vaulttest,DC=internal" \
  bindpass="pass@word1" \
  userdn="OU=Vault Users,DC=vaulttest,DC=internal" \
  userattr="sAMAccountName" \
  upndomain="vaulttest.internal" \
  groupdn="OU=Security Groups,DC=vaulttest,DC=internal"

Confirm:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
vault read /auth/ldap/config

Key                             Value
---                             -----
binddn                          CN=svc_vault_bind,OU=Service Accounts,DC=vaulttest,DC=internal
case_sensitive_names            false
certificate                     n/a
deny_null_bind                  true
discoverdn                      false
groupattr                       cn
groupdn                         OU=Security Groups,DC=vaulttest,DC=internal
groupfilter                     (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))
insecure_tls                    false
starttls                        false
tls_max_version                 tls12
tls_min_version                 tls12
token_bound_cidrs               []
token_explicit_max_ttl          0s
token_max_ttl                   0s
token_no_default_policy         false
token_num_uses                  0
token_period                    0s
token_policies                  []
token_ttl                       0s
token_type                      default
upndomain                       vaulttest.internal
url                             ldap://ec2-3-106-146-38.ap-southeast-2.compute.amazonaws.com:389
use_pre111_group_cn_behavior    false
use_token_groups                false
userattr                        samaccountname
userdn                          OU=Vault Users,DC=vaulttest,DC=internal

Reference: https://www.vaultproject.io/docs/auth/ldap.html

Configure Policies

The following assumes a Key/Value V2 secret backend at /secret.

Policy policy-vault-users will be a simple read-only policy to /secret/*.

Policy policy-vault-admins will be a simple create, update, read, list, and delete policy to /secret/*.

API:

1
2
3
4
5
6
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request PUT http://localhost:8200/v1/sys/policies/acl/policy-vault-users --data @- <<EOF
  {
    "name":"policy-vault-users",
    "policy":"path \"secret/*\" {capabilities = [\"read\", \"list\"]}"
  }
EOF
1
2
3
4
5
6
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request PUT http://localhost:8200/v1/sys/policies/acl/policy-vault-admins --data @- <<EOF
  {
    "name":"policy-vault-admins",
    "policy":"path \"secret/*\" {capabilities = [\"create\",\"update\", \"read\", \"list\", \"delete\"]}"
  }
EOF

Confirm:

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request LIST http://localhost:8200/v1/sys/policies/acl | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "request_id": "5d5b41f0-1ee1-ffed-2a7d-d6700e4f2fd3",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "keys": [
      "default",
      "policy-vault-admins",
      "policy-vault-users",
      "root"
    ]
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}
1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request GET http://localhost:8200/v1/sys/policies/acl/policy-vault-users | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "request_id": "ca258cbf-ad82-920e-5bac-679d9805074f",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "name": "policy-vault-users",
    "policy": "path \"secret/*\" {capabilities = [\"read\", \"list\"]}"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

Reference: https://www.vaultproject.io/api/system/policies.html#sys-policies-

CLI:

1
2
3
4
5
cat <<EOF | vault policy write policy-vault-users -
  path "secret/*" {
    capabilities = ["read", "list"]
  }
EOF
1
2
3
4
5
cat <<EOF | vault policy write policy-vault-admins -
  path "secret/*" {
    capabilities = ["create", "update", "read", "list", "delete"]
  }
EOF

Confirm:

1
vault policy list
1
2
3
4
default
policy-vault-admins
policy-vault-users
root
1
vault policy read policy-vault-users
1
2
3
path "secret/*" {
    capabilities = ["read", "list"]
  }
1
vault policy read policy-vault-admins
1
2
3
path "secret/*" {
    capabilities = ["create", "update", "read", "list", "delete"]
  }

Reference: https://www.vaultproject.io/docs/commands/policy/write.html & https://www.vaultproject.io/docs/concepts/policies.html

Create Identity Groups

The identity groups in Vault need to be created with a type of external to integrate with LDAP.

One group will be created for Vault Users and another for Vault Admins.

The groups will be created with the policies created previously.

1
2
3
4
5
6
7
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request POST http://localhost:8200/v1/identity/group --data @- <<EOF | jq
  {
    "name":"Vault Users",
    "type":"external",
    "policies":"policy-vault-users"
    }
EOF
1
2
3
4
5
6
7
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request POST http://localhost:8200/v1/identity/group --data @- <<EOF | jq
  {
    "name":"Vault Admins",
    "type":"external",
    "policies":"policy-vault-admins"
    }
EOF

Confirm:

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request LIST http://localhost:8200/v1/identity/group/id | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  "request_id": "1f466da1-d7b2-18b4-380b-244b1b9f2424",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "key_info": {
      "461b9477-0c66-2520-47c1-358bed5fcaac": {
        "name": "Vault Admins",
        "num_member_entities": 0,
        "num_parent_groups": 0
      },
      "fea4254d-73e8-e0f7-852e-47ad3d18856f": {
        "name": "Vault Users",
        "num_member_entities": 0,
        "num_parent_groups": 0
      }
    },
    "keys": [
      "461b9477-0c66-2520-47c1-358bed5fcaac",
      "fea4254d-73e8-e0f7-852e-47ad3d18856f"
    ]
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

Reference: https://www.vaultproject.io/api/secret/identity/group.html#create-a-group

CLI:

1
2
3
vault write identity/group name="Vault Users" \
  policies="policy-vault-users" \
  type="external"
1
2
3
vault write identity/group name="Vault Admins" \
  policies="policy-vault-admins" \
  type="external"

Confirm:

1
vault list /identity/group/name
1
2
3
4
Keys
----
Vault Admins
Vault Users
1
vault read "/identity/group/name/Vault Users"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Key                  Value
---                  -----
alias                map[]
creation_time        2019-11-07T05:36:50.825746Z
id                   2ae358f8-4520-82b3-9b67-148ee7bb609a
last_update_time     2019-11-07T05:36:50.825746Z
member_entity_ids    []
member_group_ids     <nil>
metadata             <nil>
modify_index         1
name                 Vault Users
namespace_id         root
parent_group_ids     <nil>
policies             [policy-vault-users]
type                 external
1
vault read "/identity/group/name/Vault Admins"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Key                  Value
---                  -----
alias                map[]
creation_time        2019-11-07T05:30:01.893194Z
id                   461b9477-0c66-2520-47c1-358bed5fcaac
last_update_time     2019-11-07T05:30:01.893194Z
member_entity_ids    []
member_group_ids     <nil>
metadata             <nil>
modify_index         1
name                 Vault Admins
namespace_id         root
parent_group_ids     <nil>
policies             [policy-vault-admins]
type                 external

Reference: https://www.vaultproject.io/docs/secrets/identity/index.html#identity-groups

Create Group Aliases

Group aliases will be created for both of the LDAP security groups mapping them to the Vault identity groups. The group alias name must match the security group CN (common name) in LDAP.

First, determine the mount accessor for the LDAP auth method

API:

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request GET http://localhost:8200/v1/sys/auth | jq -r '.["ldap/"].accessor'
1
auth_ldap_e620cbc4

CLI:

1
vault auth list -format=json | jq -r '.["ldap/"].accessor'
1
auth_ldap_e620cbc4

Then determine the id of each of the groups (note from prior API/CLI calls or do the below).

API:

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request GET --url "http://localhost:8200/v1/identity/group/name/Vault%20Users" | jq ".data.id"
1
"2ae358f8-4520-82b3-9b67-148ee7bb609a"
1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request GET --url "http://localhost:8200/v1/identity/group/name/Vault%20Admins" | jq ".data.id"
1
"461b9477-0c66-2520-47c1-358bed5fcaac"

CLI:

1
vault read "/identity/group/name/Vault Users" -format=json | jq ".data.id"
1
"2ae358f8-4520-82b3-9b67-148ee7bb609a"
1
vault read "/identity/group/name/Vault Admins" -format=json | jq ".data.id"
1
"461b9477-0c66-2520-47c1-358bed5fcaac"

Then create the group aliases

API:

1
2
3
4
5
6
7
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request POST http://localhost:8200/v1/identity/group-alias --data @- <<EOF | jq
{
  "name":"Vault Users",
  "mount_accessor":"auth_ldap_e620cbc4",
  "canonical_id":"2ae358f8-4520-82b3-9b67-148ee7bb609a"
}
EOF
1
2
3
4
5
6
7
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request POST http://localhost:8200/v1/identity/group-alias --data @- <<EOF | jq
{
  "name":"Vault Admins",
  "mount_accessor":"auth_ldap_e620cbc4",
  "canonical_id":"461b9477-0c66-2520-47c1-358bed5fcaac"
}
EOF

Confirm:

1
curl --header "X-Vault-Token: s.NQWnh1WyIJEvbcKcE3YGWLei" --request LIST http://localhost:8200/v1/identity/group-alias/id/ | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "request_id": "575942d3-2193-781a-3f62-fa1d6d101ec6",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "key_info": {
      "08cd4481-5eff-fadd-bce4-08862cba03a5": {
        "canonical_id": "461b9477-0c66-2520-47c1-358bed5fcaac",
        "mount_accessor": "auth_ldap_e620cbc4",
        "mount_path": "auth/ldap/",
        "mount_type": "ldap",
        "name": "Vault Admins"
      },
      "48859a80-b6a5-daf5-1b47-0f09aa2e9194": {
        "canonical_id": "2ae358f8-4520-82b3-9b67-148ee7bb609a",
        "mount_accessor": "auth_ldap_e620cbc4",
        "mount_path": "auth/ldap/",
        "mount_type": "ldap",
        "name": "Vault Users"
      }
    },
    "keys": [
      "08cd4481-5eff-fadd-bce4-08862cba03a5",
      "48859a80-b6a5-daf5-1b47-0f09aa2e9194"
    ]
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

CLI:

1
2
3
vault write /identity/group-alias name="Vault Users" \
  mount_accessor="auth_ldap_e620cbc4" \
  canonical_id="2ae358f8-4520-82b3-9b67-148ee7bb609a"
1
2
3
vault write /identity/group-alias name="Vault Admins" \
  mount_accessor="auth_ldap_e620cbc4" \
  canonical_id="461b9477-0c66-2520-47c1-358bed5fcaac"

Confirm:

1
vault list /identity/group-alias/id
1
2
3
4
Keys
----
08cd4481-5eff-fadd-bce4-08862cba03a5
d77609a5-0cf2-4e4a-fd4c-bb99257a86ec
1
vault read /identity/group-alias/id/08cd4481-5eff-fadd-bce4-08862cba03a5 -format=json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "request_id": "8705e6a5-89b8-0f74-c296-47cd3086e01d",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "canonical_id": "461b9477-0c66-2520-47c1-358bed5fcaac",
    "creation_time": "2019-11-07T10:18:38.125507Z",
    "id": "08cd4481-5eff-fadd-bce4-08862cba03a5",
    "last_update_time": "2019-11-07T10:46:57.690064Z",
    "merged_from_canonical_ids": null,
    "metadata": null,
    "mount_accessor": "auth_ldap_e620cbc4",
    "mount_path": "auth/ldap/",
    "mount_type": "ldap",
    "name": "Vault Admins",
    "namespace_id": "root"
  },
  "warnings": null
}
1
vault read /identity/group-alias/id/d77609a5-0cf2-4e4a-fd4c-bb99257a86ec -format=json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "request_id": "9289cfe1-61b2-2f2b-fa7d-da77b82fd57c",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "canonical_id": "2ae358f8-4520-82b3-9b67-148ee7bb609a",
    "creation_time": "2019-11-07T11:04:11.588927Z",
    "id": "d77609a5-0cf2-4e4a-fd4c-bb99257a86ec",
    "last_update_time": "2019-11-07T11:04:11.588982Z",
    "merged_from_canonical_ids": null,
    "metadata": null,
    "mount_accessor": "auth_ldap_e620cbc4",
    "mount_path": "auth/ldap/",
    "mount_type": "ldap",
    "name": "Vault Users",
    "namespace_id": "root"
  },
  "warnings": null
}

Conclusion

At this point, vaultuser can log in and see data in secret/, but it cannot create or update. User vaultadmin can log in and can perform any of the create, read, update, list, delete actions.

References

  • Identity: Entities and Groups found here

Updated January 2020

Rather than using identity groups and group aliases you can simply configure LDAP groups.

Create the LDAP Group in Vault.

API:

1
curl --header "X-Vault-Token: xxx" --request POST https://localhost:8200/v1/auth/ldap/groups/SomeLDAPGroupName --data '{"policies": ["policy1", "policy2"]}' --insecure

Confirm:

1
curl --header "X-Vault-Token: xxx" --request GET https://localhost:8200/v1/auth/ldap/groups/SomeLDAPGroupName --insecure | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "request_id": "8d4f5858-5ae1-ce67-44d4-3e6278d20bec",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "policies": [
      "policy1",
      "policy2"
    ]
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

CLI:

1
vault write /auth/ldap/groups/SomeLDAPGroupName policies=policy1

Confirm:

1
2
3
4
vault.exe read /auth/ldap/groups/SomeLDAPGroupName
Key         Value
---         -----
policies    [policy1]