Merge pull request #6222 from hashicorp/llb-vault-ldap

Vault LDAP Credential Library
This commit is contained in:
dani authored and GitHub committed 2025-11-06 08:35:59 -05:00
commit 3461cc9b5e
81 files changed
+7674 -408

No files matched your search

+35 -14
View File
@@ -74,26 +74,47 @@ jobs:
To understand how these diffs are generated and some limitations see the
[documentation](https://github.com/hashicorp/boundary/blob/main/scripts/schema-diff.sh) of the script.
#### Functions
$(getdiff .schema-diff/funcs.diff)
<details>
<summary><b>Functions</b></summary>
#### Tables
$(getdiff .schema-diff/tables.diff)
$(getdiff .schema-diff/funcs.diff)
</details>
#### Views
$(getdiff .schema-diff/views.diff)
<details>
<summary><b>Tables</b></summary>
#### Triggers
$(getdiff .schema-diff/triggers.diff)
$(getdiff .schema-diff/tables.diff)
</details>
#### Indexes
$(getdiff .schema-diff/indexes.diff)
<details>
<summary><b>Views</b></summary>
#### Constraints
$(getdiff .schema-diff/constraints.diff)
$(getdiff .schema-diff/views.diff)
</details>
#### Foreign Key Constraints
$(getdiff .schema-diff/fk_constraints.diff)
<details>
<summary><b>Triggers</b></summary>
$(getdiff .schema-diff/triggers.diff)
</details>
<details>
<summary><b>Indexes</b></summary>
$(getdiff .schema-diff/indexes.diff)
</details>
<details>
<summary><b>Constraints</b></summary>
$(getdiff .schema-diff/constraints.diff)
</details>
<details>
<summary><b>Foreign Key Constraints</b></summary>
$(getdiff .schema-diff/fk_constraints.diff)
</details>
EOF
# Parse it through jq to build a valid json object.
+12
View File
@@ -383,6 +383,18 @@ func WithVaultCredentialLibraryPath(inPath string) Option {
}
}
func WithVaultLdapCredentialLibraryPath(inPath string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = any(map[string]any{})
}
val := raw.(map[string]any)
val["path"] = inPath
o.postMap["attributes"] = val
}
}
func WithVaultSSHCertificateCredentialLibraryPath(inPath string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
@@ -0,0 +1,40 @@
// Code generated by "make api"; DO NOT EDIT.
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package credentiallibraries
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
type VaultLdapCredentialLibraryAttributes struct {
Path string `json:"path,omitempty"`
}
func AttributesMapToVaultLdapCredentialLibraryAttributes(in map[string]any) (*VaultLdapCredentialLibraryAttributes, error) {
if in == nil {
return nil, fmt.Errorf("nil input map")
}
var out VaultLdapCredentialLibraryAttributes
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &out,
TagName: "json",
})
if err != nil {
return nil, fmt.Errorf("error creating mapstructure decoder: %w", err)
}
if err := dec.Decode(in); err != nil {
return nil, fmt.Errorf("error decoding: %w", err)
}
return &out, nil
}
func (pt *CredentialLibrary) GetVaultLdapCredentialLibraryAttributes() (*VaultLdapCredentialLibraryAttributes, error) {
if pt.Type != "vault-ldap" {
return nil, fmt.Errorf("asked to fetch %s-type attributes but credential-library is of type %s", "vault-ldap", pt.Type)
}
return AttributesMapToVaultLdapCredentialLibraryAttributes(pt.Attributes)
}
@@ -113,6 +113,17 @@ scenario "e2e_docker_base_with_vault" {
}
}
step "create_ldap_server" {
module = module.docker_ldap
depends_on = [
step.create_docker_network
]
variables {
image_name = "${var.docker_mirror}/osixia/openldap:latest"
network_name = [local.network_cluster]
}
}
step "run_e2e_test" {
module = module.test_e2e_docker
depends_on = [
@@ -141,6 +152,13 @@ scenario "e2e_docker_base_with_vault" {
vault_root_token = step.create_vault.token
vault_port = step.create_vault.port
max_page_size = step.create_boundary.max_page_size
ldap_address = step.create_ldap_server.address
ldap_domain_dn = step.create_ldap_server.domain_dn
ldap_admin_dn = step.create_ldap_server.admin_dn
ldap_admin_password = step.create_ldap_server.admin_password
ldap_user_name = step.create_ldap_server.user_name
ldap_user_password = step.create_ldap_server.user_password
ldap_group_name = step.create_ldap_server.group_name
}
}
}
+6
View File
@@ -71,6 +71,8 @@ const (
VaultSshCertificateCredentialLibraryPrefix = "clvsclt"
// DynamicCredentialPrefix is the prefix for Vault dynamic credentials
VaultDynamicCredentialPrefix = "cdvlt"
// VaultLdapCredentialLibraryPrefix is the prefix for Vault Ldap credential libraries
VaultLdapCredentialLibraryPrefix = "clvllt"
// UsernamePasswordCredentialPrefix is the prefix for username/password
// creds
@@ -243,6 +245,10 @@ var prefixToResourceType = map[string]ResourceInfo{
Type: resource.CredentialLibrary,
Subtype: UnknownSubtype,
},
VaultLdapCredentialLibraryPrefix: {
Type: resource.CredentialLibrary,
Subtype: UnknownSubtype,
},
VaultDynamicCredentialPrefix: {
Type: resource.Credential,
Subtype: UnknownSubtype,
+2 -2
View File
@@ -16,6 +16,7 @@ require (
github.com/fatih/structs v1.1.0
github.com/favadi/protoc-go-inject-tag v1.4.0
github.com/glebarez/sqlite v1.11.0
github.com/go-ldap/ldap/v3 v3.4.11
github.com/golang-migrate/migrate/v4 v4.19.0
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9
github.com/golang/protobuf v1.5.4
@@ -139,10 +140,9 @@ require (
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-ldap/ldap/v3 v3.4.8 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
+4 -20
View File
@@ -128,16 +128,15 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -184,8 +183,6 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
@@ -494,8 +491,6 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -504,10 +499,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
@@ -577,9 +569,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -603,7 +593,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -612,10 +601,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -656,7 +642,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
@@ -667,7 +652,6 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+16
View File
@@ -641,6 +641,22 @@ var inputStructs = []*structInfo{
mapstructureConversionTemplate,
},
},
{
inProto: &credentiallibraries.VaultLdapCredentialLibraryAttributes{},
outFile: "credentiallibraries/vault_ldap_credential_library_attributes.gen.go",
subtypeName: "VaultLdapCredentialLibrary",
subtype: "vault-ldap",
fieldOverrides: []fieldInfo{
{
Name: "Path",
SkipDefault: true,
},
},
parentTypeName: "CredentialLibrary",
templates: []*template.Template{
mapstructureConversionTemplate,
},
},
{
inProto: &credentiallibraries.CredentialLibrary{},
outFile: "credentiallibraries/credential_library.gen.go",
+12
View File
@@ -495,6 +495,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "create",
}
}),
"credential-libraries create vault-ldap": wrapper.Wrap(func() wrapper.WrappableCommand {
return &credentiallibrariescmd.VaultLdapCommand{
Command: base.NewCommand(ui, opts...),
Func: "create",
}
}),
"credential-libraries update": wrapper.Wrap(func() wrapper.WrappableCommand {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui, opts...),
@@ -519,6 +525,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "update",
}
}),
"credential-libraries update vault-ldap": wrapper.Wrap(func() wrapper.WrappableCommand {
return &credentiallibrariescmd.VaultLdapCommand{
Command: base.NewCommand(ui, opts...),
Func: "update",
}
}),
"credential-stores": func() (cli.Command, error) {
return &credentialstorescmd.Command{
@@ -151,6 +151,8 @@ func printItemTable(item *credentiallibraries.CredentialLibrary, resp *api.Respo
keySubstMap = genericKeySubstMap
case "vault-ssh-certificate":
keySubstMap = sshCertKeySubstMap
case "vault-ldap":
keySubstMap = ldapKeySubstMap
}
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
@@ -218,3 +220,7 @@ var sshCertKeySubstMap = map[string]string{
"critical_options": "Critical Options",
"extensions": "Extensions",
}
var ldapKeySubstMap = map[string]string{
"path": "Path",
}
@@ -0,0 +1,278 @@
// Code generated by "make cli"; DO NOT EDIT.
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package credentiallibrariescmd
import (
"errors"
"fmt"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/common"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initVaultLdapFlags() {
flagsOnce.Do(func() {
extraFlags := extraVaultLdapActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsVaultLdapMap[k] = append(flagsVaultLdapMap[k], v...)
}
})
}
var (
_ cli.Command = (*VaultLdapCommand)(nil)
_ cli.CommandAutocomplete = (*VaultLdapCommand)(nil)
)
type VaultLdapCommand struct {
*base.Command
Func string
plural string
extraVaultLdapCmdVars
}
func (c *VaultLdapCommand) AutocompleteArgs() complete.Predictor {
initVaultLdapFlags()
return complete.PredictAnything
}
func (c *VaultLdapCommand) AutocompleteFlags() complete.Flags {
initVaultLdapFlags()
return c.Flags().Completions()
}
func (c *VaultLdapCommand) Synopsis() string {
if extra := extraVaultLdapSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential-library"
synopsisStr = fmt.Sprintf("%s %s", "vault-ldap-type", synopsisStr)
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *VaultLdapCommand) Help() string {
initVaultLdapFlags()
var helpStr string
helpMap := common.HelpMap("credential library")
switch c.Func {
default:
helpStr = c.extraVaultLdapHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsVaultLdapMap = map[string][]string{
"create": {"credential-store-id", "name", "description"},
"update": {"id", "name", "description", "version"},
}
func (c *VaultLdapCommand) Flags() *base.FlagSets {
if len(flagsVaultLdapMap[c.Func]) == 0 {
return c.FlagSet(base.FlagSetNone)
}
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
common.PopulateCommonFlags(c.Command, f, "vault-ldap-type credential library", flagsVaultLdapMap, c.Func)
extraVaultLdapFlagsFunc(c, set, f)
return set
}
func (c *VaultLdapCommand) Run(args []string) int {
initVaultLdapFlags()
switch c.Func {
case "":
return cli.RunResultHelp
}
c.plural = "vault-ldap-type credential library"
switch c.Func {
case "list":
c.plural = "vault-ldap-type credential libraries"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsVaultLdapMap[c.Func], "id") && c.FlagId == "" {
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
return base.CommandUserError
}
var opts []credentiallibraries.Option
if strutil.StrListContains(flagsVaultLdapMap[c.Func], "credential-store-id") {
switch c.Func {
case "create":
if c.FlagCredentialStoreId == "" {
c.PrintCliError(errors.New("CredentialStore ID must be passed in via -credential-store-id or BOUNDARY_CREDENTIAL_STORE_ID"))
return base.CommandUserError
}
}
}
client, err := c.Client()
if c.WrapperCleanupFunc != nil {
defer func() {
if err := c.WrapperCleanupFunc(); err != nil {
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
}
}()
}
if err != nil {
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
return base.CommandCliError
}
credentiallibrariesClient := credentiallibraries.NewClient(client)
switch c.FlagName {
case "":
case "null":
opts = append(opts, credentiallibraries.DefaultName())
default:
opts = append(opts, credentiallibraries.WithName(c.FlagName))
}
switch c.FlagDescription {
case "":
case "null":
opts = append(opts, credentiallibraries.DefaultDescription())
default:
opts = append(opts, credentiallibraries.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, credentiallibraries.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
case "update":
switch c.FlagVersion {
case 0:
opts = append(opts, credentiallibraries.WithAutomaticVersioning(true))
default:
version = uint32(c.FlagVersion)
}
}
if ok := extraVaultLdapFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var resp *api.Response
var item *credentiallibraries.CredentialLibrary
var createResult *credentiallibraries.CredentialLibraryCreateResult
var updateResult *credentiallibraries.CredentialLibraryUpdateResult
switch c.Func {
case "create":
createResult, err = credentiallibrariesClient.Create(c.Context, "vault-ldap", c.FlagCredentialStoreId, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = createResult.GetResponse()
item = createResult.GetItem()
case "update":
updateResult, err = credentiallibrariesClient.Update(c.Context, c.FlagId, version, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = updateResult.GetResponse()
item = updateResult.GetItem()
}
resp, item, err = executeExtraVaultLdapActions(c, resp, item, err, credentiallibrariesClient, version, opts)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
output, err := printCustomVaultLdapActionOutput(c)
if err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if output {
return base.CommandSuccess
}
switch c.Func {
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(printItemTable(item, resp))
case "json":
if ok := c.PrintJsonItem(resp); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
func (c *VaultLdapCommand) checkFuncError(err error) int {
if err == nil {
return 0
}
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
var (
extraVaultLdapActionsFlagsMapFunc = func() map[string][]string { return nil }
extraVaultLdapSynopsisFunc = func(*VaultLdapCommand) string { return "" }
extraVaultLdapFlagsFunc = func(*VaultLdapCommand, *base.FlagSets, *base.FlagSet) {}
extraVaultLdapFlagsHandlingFunc = func(*VaultLdapCommand, *base.FlagSets, *[]credentiallibraries.Option) bool { return true }
executeExtraVaultLdapActions = func(_ *VaultLdapCommand, inResp *api.Response, inItem *credentiallibraries.CredentialLibrary, inErr error, _ *credentiallibraries.Client, _ uint32, _ []credentiallibraries.Option) (*api.Response, *credentiallibraries.CredentialLibrary, error) {
return inResp, inItem, inErr
}
printCustomVaultLdapActionOutput = func(*VaultLdapCommand) (bool, error) { return false, nil }
)
@@ -0,0 +1,83 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package credentiallibrariescmd
import (
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
)
func init() {
extraVaultLdapActionsFlagsMapFunc = extraVaultLdapActionsFlagsMapFuncImpl
extraVaultLdapFlagsFunc = extraVaultLdapFlagsFuncImpl
extraVaultLdapFlagsHandlingFunc = extraVaultLdapFlagHandlingFuncImpl
}
type extraVaultLdapCmdVars struct {
flagPath string
}
func extraVaultLdapActionsFlagsMapFuncImpl() map[string][]string {
flags := map[string][]string{
"create": {
pathFlagName,
},
"update": {
pathFlagName,
},
}
return flags
}
func extraVaultLdapFlagsFuncImpl(c *VaultLdapCommand, set *base.FlagSets, _ *base.FlagSet) {
f := set.NewFlagSet("Vault Ldap Credential Library Options")
for _, name := range flagsVaultLdapMap[c.Func] {
switch name {
case pathFlagName:
f.StringVar(&base.StringVar{
Name: pathFlagName,
Target: &c.flagPath,
Usage: "The path in vault to request credentials from.",
})
}
}
}
func extraVaultLdapFlagHandlingFuncImpl(c *VaultLdapCommand, _ *base.FlagSets, opts *[]credentiallibraries.Option) bool {
switch c.flagPath {
case "":
default:
*opts = append(*opts, credentiallibraries.WithVaultLdapCredentialLibraryPath(c.flagPath))
}
return true
}
func (c *VaultLdapCommand) extraVaultLdapHelpFunc(_ map[string]func() string) string {
var helpStr string
switch c.Func {
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries create vault-ldap -credential-store-id [options] [args]",
"",
" Create a vault-ldap-type credential library. Example:",
"",
` $ boundary credential-libraries create vault-ldap -credential-store-id csvlt_1234567890 -vault-path "/ldap/static-cred/einstein"`,
"",
"",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries update vault-ldap [options] [args]",
"",
" Update a vault-ldap-type credential library given its ID. Example:",
"",
` $ boundary credential-libraries update vault-ldap -id clvllt_1234567890 -name devops -description "For DevOps usage"`,
"",
"",
})
}
return helpStr + c.Flags().Help()
}
+16
View File
@@ -357,6 +357,22 @@ var inputStructs = map[string][]*cmdInfo{
VersionedActions: []string{"update"},
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
{
ResourceType: resource.CredentialLibrary.String(),
Pkg: "credentiallibraries",
StdActions: []string{"create", "update"},
SubActionPrefix: "vault-ldap",
HasExtraCommandVars: true,
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
NeedsSubtypeInCreate: true,
Container: "CredentialStore",
VersionedActions: []string{"update"},
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
},
"credentials": {
{
@@ -48,8 +48,10 @@ func TestLibraryService_List(t *testing.T) {
vault.TestCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), globals.UnspecifiedCredentialType, 1)[0],
vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 1)[0],
vault.TestCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), globals.UnspecifiedCredentialType, 1)[0],
vault.TestLdapCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 1)[0],
vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 1)[0],
vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 1)[0],
vault.TestLdapCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 1)[0],
}
// since we sort by create time descending, we need to reverse the slice
@@ -66,7 +68,9 @@ func TestLibraryService_List(t *testing.T) {
cmpopts.IgnoreUnexported(
vault.CredentialLibrary{},
vault.SSHCertificateCredentialLibrary{},
vault.LdapCredentialLibrary{},
store.SSHCertificateCredentialLibrary{},
store.LdapCredentialLibrary{},
store.CredentialLibrary{},
timestamp.Timestamp{},
timestamppb.Timestamp{},
@@ -385,7 +389,7 @@ func TestLibraryService_List(t *testing.T) {
require.NotNil(t, resp.ListToken)
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp.CompleteListing)
require.Equal(t, resp.EstimatedItemCount, 5)
require.Equal(t, resp.EstimatedItemCount, 7)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], libs[0], cmpOpts...))
@@ -394,7 +398,7 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 5)
require.Equal(t, resp2.EstimatedItemCount, 7)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 1)
require.Empty(t, cmp.Diff(resp2.Items[0], libs[1], cmpOpts...))
@@ -403,7 +407,7 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 5)
require.Equal(t, resp3.EstimatedItemCount, 7)
require.Empty(t, resp3.DeletedIds)
require.Len(t, resp3.Items, 1)
require.Empty(t, cmp.Diff(resp3.Items[0], libs[2], cmpOpts...))
@@ -412,7 +416,7 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp4.CompleteListing)
require.Equal(t, resp4.EstimatedItemCount, 5)
require.Equal(t, resp4.EstimatedItemCount, 7)
require.Empty(t, resp4.DeletedIds)
require.Len(t, resp4.Items, 1)
require.Empty(t, cmp.Diff(resp4.Items[0], libs[3], cmpOpts...))
@@ -420,21 +424,39 @@ func TestLibraryService_List(t *testing.T) {
resp5, err := credential.ListLibrariesPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp5.CompleteListing)
require.Equal(t, resp5.EstimatedItemCount, 5)
require.False(t, resp5.CompleteListing)
require.Equal(t, resp5.EstimatedItemCount, 7)
require.Empty(t, resp5.DeletedIds)
require.Len(t, resp5.Items, 1)
require.Empty(t, cmp.Diff(resp5.Items[0], libs[4], cmpOpts...))
// Finished initial pagination phase, request refresh
// Expect no results.
resp6, err := credential.ListLibrariesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp5.ListToken, repo, credStore.GetPublicId())
resp6, err := credential.ListLibrariesPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp6.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp6.CompleteListing)
require.Equal(t, resp6.EstimatedItemCount, 5)
require.False(t, resp6.CompleteListing)
require.Equal(t, resp6.EstimatedItemCount, 7)
require.Empty(t, resp6.DeletedIds)
require.Empty(t, resp6.Items)
require.Len(t, resp6.Items, 1)
require.Empty(t, cmp.Diff(resp6.Items[0], libs[5], cmpOpts...))
resp7, err := credential.ListLibrariesPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp7.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp7.CompleteListing)
require.Equal(t, resp7.EstimatedItemCount, 7)
require.Empty(t, resp7.DeletedIds)
require.Len(t, resp7.Items, 1)
require.Empty(t, cmp.Diff(resp7.Items[0], libs[6], cmpOpts...))
// Finished initial pagination phase, request refresh
// Expect no results.
resp8, err := credential.ListLibrariesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp5.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp8.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp8.CompleteListing)
require.Equal(t, resp8.EstimatedItemCount, 7)
require.Empty(t, resp8.DeletedIds)
require.Empty(t, resp8.Items)
// Create some new libraries
newLib1 := vault.TestCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), globals.UnspecifiedCredentialType, 1)[0]
@@ -453,33 +475,33 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
// Refresh again, should get newLib2
resp7, err := credential.ListLibrariesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp6.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp7.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp7.CompleteListing)
require.Equal(t, resp7.EstimatedItemCount, 7)
require.Empty(t, resp7.DeletedIds)
require.Len(t, resp7.Items, 1)
require.Empty(t, cmp.Diff(resp7.Items[0], newLib2, cmpOpts...))
// Refresh again, should get newLib1
resp8, err := credential.ListLibrariesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp7.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp8.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp8.CompleteListing)
require.Equal(t, resp8.EstimatedItemCount, 7)
require.Empty(t, resp8.DeletedIds)
require.Len(t, resp8.Items, 1)
require.Empty(t, cmp.Diff(resp8.Items[0], newLib1, cmpOpts...))
// Refresh again, should get no results
resp9, err := credential.ListLibrariesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp8.ListToken, repo, credStore.GetPublicId())
resp9, err := credential.ListLibrariesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp6.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp9.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp9.CompleteListing)
require.Equal(t, resp9.EstimatedItemCount, 7)
require.False(t, resp9.CompleteListing)
require.Equal(t, resp9.EstimatedItemCount, 9)
require.Empty(t, resp9.DeletedIds)
require.Empty(t, resp9.Items)
require.Len(t, resp9.Items, 1)
require.Empty(t, cmp.Diff(resp9.Items[0], newLib2, cmpOpts...))
// Refresh again, should get newLib1
resp10, err := credential.ListLibrariesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp7.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp10.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp10.CompleteListing)
require.Equal(t, resp10.EstimatedItemCount, 9)
require.Empty(t, resp10.DeletedIds)
require.Len(t, resp10.Items, 1)
require.Empty(t, cmp.Diff(resp10.Items[0], newLib1, cmpOpts...))
// Refresh again, should get no results
resp11, err := credential.ListLibrariesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp8.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp11.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp11.CompleteListing)
require.Equal(t, resp11.EstimatedItemCount, 9)
require.Empty(t, resp11.DeletedIds)
require.Empty(t, resp11.Items)
})
t.Run("simple pagination with aggressive filtering", func(t *testing.T) {
@@ -492,7 +514,7 @@ func TestLibraryService_List(t *testing.T) {
require.NotNil(t, resp.ListToken)
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp.CompleteListing)
require.Equal(t, resp.EstimatedItemCount, 5)
require.Equal(t, resp.EstimatedItemCount, 7)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], libs[1], cmpOpts...))
@@ -502,7 +524,7 @@ func TestLibraryService_List(t *testing.T) {
require.NotNil(t, resp2.ListToken)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 5)
require.Equal(t, resp2.EstimatedItemCount, 7)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 1)
require.Empty(t, cmp.Diff(resp2.Items[0], libs[len(libs)-1], cmpOpts...))
@@ -512,7 +534,7 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 5)
require.Equal(t, resp3.EstimatedItemCount, 7)
require.Empty(t, resp3.DeletedIds)
require.Empty(t, resp3.Items)
@@ -545,7 +567,7 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp4.CompleteListing)
require.Equal(t, resp4.EstimatedItemCount, 9)
require.Equal(t, resp4.EstimatedItemCount, 11)
require.Empty(t, resp4.DeletedIds)
require.Len(t, resp4.Items, 1)
require.Empty(t, cmp.Diff(resp4.Items[0], newLibs2[0], cmpOpts...))
@@ -555,7 +577,7 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp5.CompleteListing)
require.Equal(t, resp5.EstimatedItemCount, 9)
require.Equal(t, resp5.EstimatedItemCount, 11)
require.Empty(t, resp5.DeletedIds)
require.Len(t, resp5.Items, 1)
require.Empty(t, cmp.Diff(resp5.Items[0], newLibs1[0], cmpOpts...))
@@ -566,7 +588,7 @@ func TestLibraryService_List(t *testing.T) {
return true, nil
}
deletedCredentialLibraryId := libs[0].GetPublicId()
_, err := repo.DeleteSSHCertificateCredentialLibrary(ctx, prj.GetPublicId(), deletedCredentialLibraryId)
_, err := repo.DeleteLdapCredentialLibrary(ctx, prj.GetPublicId(), deletedCredentialLibraryId)
require.NoError(t, err)
libs = libs[1:]
@@ -579,19 +601,19 @@ func TestLibraryService_List(t *testing.T) {
require.NotNil(t, resp.ListToken)
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp.CompleteListing)
require.Equal(t, resp.EstimatedItemCount, 4)
require.Equal(t, resp.EstimatedItemCount, 6)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], libs[0], cmpOpts...))
// request remaining results
resp2, err := credential.ListLibrariesPage(ctx, []byte("some hash"), 3, filterFunc, resp.ListToken, repo, credStore.GetPublicId())
resp2, err := credential.ListLibrariesPage(ctx, []byte("some hash"), 5, filterFunc, resp.ListToken, repo, credStore.GetPublicId())
require.NoError(t, err)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 4)
require.Equal(t, resp2.EstimatedItemCount, 6)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 3)
require.Len(t, resp2.Items, 5)
require.Empty(t, cmp.Diff(resp2.Items, libs[1:], cmpOpts...))
deletedCredentialLibraryId = libs[0].GetPublicId()
@@ -608,7 +630,7 @@ func TestLibraryService_List(t *testing.T) {
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 3)
require.Equal(t, resp3.EstimatedItemCount, 5)
require.Contains(t, resp3.DeletedIds, deletedCredentialLibraryId)
require.Empty(t, resp3.Items)
})
@@ -98,7 +98,7 @@ func (l *CredentialLibrary) TableName() string {
if l.tableName != "" {
return l.tableName
}
return "credential_vault_library"
return "credential_vault_generic_library"
}
// SetTableName sets the table name.
@@ -206,6 +206,20 @@ func (l *listCredentialLibraryResult) toLibrary(ctx context.Context) (credential
AdditionalValidPrincipals: l.AdditionalValidPrincipals,
},
}, nil
case "ldap":
return &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
PublicId: l.PublicId,
StoreId: l.StoreId,
Name: l.Name,
Description: l.Description,
CreateTime: l.CreateTime,
UpdateTime: l.UpdateTime,
Version: uint32(l.Version),
VaultPath: l.VaultPath,
CredentialType: l.CredentialType,
},
}, nil
default:
return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unexpected vault credential library type %s returned", l.Type))
}
@@ -218,5 +232,5 @@ type deletedCredentialLibrary struct {
// TableName returns the tablename to override the default gorm table name
func (s *deletedCredentialLibrary) TableName() string {
return "credential_vault_library_deleted"
return "credential_vault_generic_library_deleted"
}
+8 -2
View File
@@ -6,8 +6,9 @@ package vault
import "testing"
var (
newVaultServer func(t testing.TB, opt ...TestOption) *TestVaultServer = skipNewServer
mountDatabase func(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestDatabase = skipMountDatabase
newVaultServer func(t testing.TB, opt ...TestOption) *TestVaultServer = skipNewServer
mountLdapServer func(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestLdapServer = skipMountLdapServer
mountDatabase func(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestDatabase = skipMountDatabase
)
func skipNewServer(t testing.TB, opt ...TestOption) *TestVaultServer {
@@ -19,3 +20,8 @@ func skipMountDatabase(t testing.TB, v *TestVaultServer, opt ...TestOption) *Tes
t.Skip("docker not available")
return nil
}
func skipMountLdapServer(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestLdapServer {
t.Skip("docker not available")
return nil
}
@@ -0,0 +1,107 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package vault
import (
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/types/resource"
"google.golang.org/protobuf/proto"
)
var _ credential.Library = (*LdapCredentialLibrary)(nil)
// LdapCredentialLibrary is a credential library that issues ldap credentials
// using the vault ldap secret engine. This credential library always issues
// username/password/domain credentials.
type LdapCredentialLibrary struct {
*store.LdapCredentialLibrary
tableName string `gorm:"-"`
}
// NewLdapCredentialLibrary creates a new in memory LdapCredentialLibrary for a
// Vault backend at vaultPath assigned to storeId. This credential library
// always issues username/password/domain credentials. WithName and
// WithDescription are the only valid options. All other options are ignored.
func NewLdapCredentialLibrary(storeId string, vaultPath string, opt ...Option) (*LdapCredentialLibrary, error) {
const op = "vault.NewLdapCredentialLibrary"
opts := getOpts(opt...)
l := &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: storeId,
Name: opts.withName,
Description: opts.withDescription,
VaultPath: vaultPath,
CredentialType: string(globals.UsernamePasswordDomainCredentialType),
},
}
return l, nil
}
func allocLdapCredentialLibrary() *LdapCredentialLibrary {
return &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{},
}
}
func (l *LdapCredentialLibrary) clone() *LdapCredentialLibrary {
cp := proto.Clone(l.LdapCredentialLibrary)
return &LdapCredentialLibrary{
LdapCredentialLibrary: cp.(*store.LdapCredentialLibrary),
}
}
func (l *LdapCredentialLibrary) setId(i string) {
l.PublicId = i
}
// TableName returns the table name.
func (l *LdapCredentialLibrary) TableName() string {
if l.tableName != "" {
return l.tableName
}
return "credential_vault_ldap_library"
}
// SetTableName sets the table name.
func (l *LdapCredentialLibrary) SetTableName(n string) {
l.tableName = n
}
// GetResourceType returns the resource type of the CredentialLibrary
func (l *LdapCredentialLibrary) GetResourceType() resource.Type {
return resource.CredentialLibrary
}
func (l *LdapCredentialLibrary) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{l.PublicId},
"resource-type": []string{"credential-vault-ldap-library"},
"op-type": []string{op.String()},
}
if l.StoreId != "" {
metadata["store-id"] = []string{l.StoreId}
}
return metadata
}
// CredentialType returns the type of credential the library retrieves.
func (l *LdapCredentialLibrary) CredentialType() globals.CredentialType {
return globals.CredentialType(l.LdapCredentialLibrary.CredentialType)
}
type deletedLdapCredentialLibrary struct {
PublicId string `gorm:"primary_key"`
DeleteTime *timestamp.Timestamp
}
// TableName returns the tablename to override the default gorm table name
func (s *deletedLdapCredentialLibrary) TableName() string {
return "credential_vault_ldap_library_deleted"
}
@@ -96,7 +96,7 @@ func (o *UsernamePasswordOverride) TableName() string {
if o.tableName != "" {
return o.tableName
}
return "credential_vault_library_username_password_mapping_override"
return "credential_vault_generic_library_username_password_mapping_ovrd"
}
// SetTableName sets the table name.
@@ -163,7 +163,7 @@ func (o *UsernamePasswordDomainOverride) TableName() string {
if o.tableName != "" {
return o.tableName
}
return "credential_vault_library_username_password_domain_mapping_ovrd"
return "credential_vault_generic_library_usern_pass_domain_mapping_ovrd"
}
// SetTableName sets the table name.
@@ -230,7 +230,7 @@ func (o *SshPrivateKeyOverride) TableName() string {
if o.tableName != "" {
return o.tableName
}
return "credential_vault_library_ssh_private_key_mapping_override"
return "credential_vault_generic_library_ssh_private_key_mapping_ovrd"
}
// SetTableName sets the table name.
+277 -9
View File
@@ -20,6 +20,7 @@ import (
"strings"
"time"
ldapv3 "github.com/go-ldap/ldap/v3"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/internal/sshprivatekey"
@@ -231,8 +232,10 @@ type issuingCredentialLibrary interface {
retrieveCredential(context.Context, errors.Op, ...credential.Option) (dynamicCred, error)
}
// A genericIssuingCredentialLibrary contains all the values needed to connect to Vault and
// retrieve credentials.
// genericIssuingCredentialLibrary is a subtype of
// privateCredentialLibraryAllTypes specifically for Vault generic credential
// libraries. It contains all the values needed to connect to Vault and retrieve
// credentials.
type genericIssuingCredentialLibrary struct {
PublicId string
StoreId string
@@ -446,11 +449,6 @@ func (pl *genericIssuingCredentialLibrary) retrieveCredential(ctx context.Contex
return convert(ctx, dCred)
}
// TableName returns the table name for gorm.
func (pl *genericIssuingCredentialLibrary) TableName() string {
return "credential_vault_library_issue_credentials"
}
func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []credential.Request) ([]issuingCredentialLibrary, error) {
const op = "vault.(Repository).getIssueCredLibraries"
@@ -514,8 +512,9 @@ func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []crede
return decryptedLibs, nil
}
// privateCredentialLibraryAllTypes is a clone of genericIssuingCredentialLibrary. Contains
// all the values needed to connect to Vault and retrieve credentials.
// privateCredentialLibraryAllTypes is a type that interfaces with the database.
// It contains all the values needed to connect to Vault and retrieve
// credentials.
type privateCredentialLibraryAllTypes struct {
PublicId string `gorm:"primary_key"`
StoreId string
@@ -686,6 +685,42 @@ func (pl *privateCredentialLibraryAllTypes) toTypedIssuingCredentialLibrary() is
Extensions: pl.Extensions,
AdditionalValidPrincipals: pl.AdditionalValidPrincipals,
}
case "ldap":
return &ldapIssuingCredentialLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
Username: pl.Username,
Name: pl.Name,
Description: pl.Description,
CreateTime: pl.CreateTime,
UpdateTime: pl.UpdateTime,
Version: pl.Version,
ProjectId: pl.ProjectId,
VaultPath: pl.VaultPath,
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: pl.CaCert,
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
WorkerFilter: pl.WorkerFilter,
TokenHmac: pl.TokenHmac,
Token: pl.Token,
CtToken: pl.CtToken,
TokenKeyId: pl.TokenKeyId,
ClientCert: pl.ClientCert,
ClientKey: pl.ClientKey,
CtClientKey: pl.CtClientKey,
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
KeyType: pl.KeyType,
KeyBits: pl.KeyBits,
Ttl: pl.Ttl,
KeyId: pl.KeyId,
CriticalOptions: pl.CriticalOptions,
Extensions: pl.Extensions,
AdditionalValidPrincipals: pl.AdditionalValidPrincipals,
}
default:
return &genericIssuingCredentialLibrary{
PublicId: pl.PublicId,
@@ -768,6 +803,10 @@ func (m *requestMap) get(libraryId string) []credential.Purpose {
return m.ids[libraryId]
}
// sshCertIssuingCredentialLibrary is a subtype of
// privateCredentialLibraryAllTypes specifically for Vault SSH Certificate
// credential libraries. It contains all the values needed to connect to Vault
// and retrieve credentials.
type sshCertIssuingCredentialLibrary struct {
PublicId string
StoreId string
@@ -1108,3 +1147,232 @@ func (lib *sshCertIssuingCredentialLibrary) retrieveCredential(ctx context.Conte
certificate: []byte(cert),
}, nil
}
type ldapIssuingCredentialLibrary struct {
PublicId string
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultPath string
CredType string
ProjectId string
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
Token TokenSecret
CtToken []byte
TokenHmac []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
Username string
KeyType string
KeyBits int
KeyId string
Ttl string
CriticalOptions []byte
Extensions []byte
Purpose credential.Purpose
AdditionalValidPrincipals string
}
// GetPublicId returns the credential library's id.
func (lib *ldapIssuingCredentialLibrary) GetPublicId() string { return lib.PublicId }
// GetStoreId returns the credential library's credential store id.
func (lib *ldapIssuingCredentialLibrary) GetStoreId() string { return lib.StoreId }
// GetName returns the credential library's name.
func (lib *ldapIssuingCredentialLibrary) GetName() string { return lib.Name }
// GetDescription returns the credential library's description.
func (lib *ldapIssuingCredentialLibrary) GetDescription() string { return lib.Description }
// GetVersion returns the credential library's version.
func (lib *ldapIssuingCredentialLibrary) GetVersion() uint32 { return lib.Version }
// GetPurpose returns the credential library's purpose.
func (lib *ldapIssuingCredentialLibrary) GetPurpose() credential.Purpose { return lib.Purpose }
// GetCreateTime returns the credential library's create time.
func (lib *ldapIssuingCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return lib.CreateTime }
// GetUpdateTime returns the credential library's update time.
func (lib *ldapIssuingCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return lib.UpdateTime }
// CredentialType returns the credential library's issuing credential type.
func (lib *ldapIssuingCredentialLibrary) CredentialType() globals.CredentialType {
switch ct := lib.CredType; ct {
case "":
return globals.UnspecifiedCredentialType
default:
return globals.CredentialType(ct)
}
}
// GetResourceType returns the credential library's resource type.
func (lib *ldapIssuingCredentialLibrary) GetResourceType() resource.Type {
return resource.CredentialLibrary
}
// client produces a Vault API client using this credential library's data.
func (lib *ldapIssuingCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(genericIssuingCredentialLibrary).client"
clientConfig := &clientConfig{
Addr: lib.VaultAddress,
Token: lib.Token,
CaCert: lib.CaCert,
TlsServerName: lib.TlsServerName,
TlsSkipVerify: lib.TlsSkipVerify,
Namespace: lib.Namespace,
}
if lib.ClientKey != nil {
clientConfig.ClientCert = lib.ClientCert
clientConfig.ClientKey = lib.ClientKey
}
client, err := vaultClientFactoryFn(ctx, clientConfig, WithWorkerFilter(lib.WorkerFilter))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create vault client"))
}
return client, nil
}
var vaultLdapPathRegexp = regexp.MustCompile(`^.+\/(static-cred|creds).+$`)
// retrieveCredential retrieves a dynamic LDAP credential from Vault for a
// specific session and connection.
func (lib *ldapIssuingCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, _ ...credential.Option) (dynamicCred, error) {
credId, err := newCredentialId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
client, err := lib.client(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var secret *vault.Secret
secret, err = client.get(ctx, lib.VaultPath)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if secret == nil {
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
}
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
cred, err := newCredential(ctx, lib.GetPublicId(), secret.LeaseID, lib.TokenHmac, leaseDuration)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cred.PublicId = credId
cred.IsRenewable = secret.Renewable
upd := &usrPassDomainCred{
baseCred: &baseCred{
Credential: cred,
lib: lib,
secretData: secret.Data,
},
}
username, ok := secret.Data["username"].(string)
if !ok {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a username or response was not in the expected format")
}
upd.username = username
pwData, ok := secret.Data["password"].(string)
if !ok {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a password or response was not in the expected format")
}
upd.password = credential.Password(pwData)
matches := vaultLdapPathRegexp.FindStringSubmatch(lib.VaultPath)
if len(matches) < 2 { // [0] is the vault path, [1] is static-cred or creds, if it exists.
return nil, errors.New(ctx, errors.InvalidParameter, op, "vault path was not in an expected format. expected path containing \"static-cred\" or \"creds\"")
}
switch matches[1] {
case "static-cred":
dn, ok := secret.Data["dn"].(string)
if !ok {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a dn or response was not in the expected format")
}
domain, err := extractDomainFromDn(dn)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
upd.domain = domain
case "creds":
var dns []string
dis, ok := secret.Data["distinguished_names"].([]any)
if ok {
for _, value := range dis {
if v, ok := value.(string); ok {
dns = append(dns, v)
} else {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret returned distinguished_names with non-string member")
}
}
} else {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain distinguished_names or response was not in the expected format")
}
if len(dns) == 0 {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret returned empty distinguished_names")
}
// In the Vault LDAP secrets engine, no deduplication occurs and order
// is maintained from the LDIF statements, so the assumption here is
// that the first DN should correspond to the user's creation in the
// creation LDIF, which should correspond to the correct domain.
// https://developer.hashicorp.com/vault/docs/secrets/ldap#dynamic-credentials
domain, err := extractDomainFromDn(dns[0])
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
upd.domain = domain
}
return upd, nil
}
// extractDomainFromDn composes a domain name from all the "dc" attributes in an
// LDAP DN.
func extractDomainFromDn(dnStr string) (string, error) {
if dnStr == "" {
return "", fmt.Errorf("empty DN")
}
dn, err := ldapv3.ParseDN(dnStr)
if err != nil {
return "", fmt.Errorf("failed to parse DN: %w", err)
}
var dcs []string
for _, rdn := range dn.RDNs {
for _, attr := range rdn.Attributes {
// "0.9.2342.19200300.100.1.25" is the OID for the "dc" attribute
// and it is a valid representation in a DN string.
// (https://docs.ldap.com/specs/rfc4519.txt)
if strings.EqualFold(attr.Type, "dc") || strings.EqualFold(attr.Type, "0.9.2342.19200300.100.1.25") {
dcs = append(dcs, attr.Value)
}
}
}
if len(dcs) == 0 {
return "", fmt.Errorf("no domain component (dc) attribute found in dn")
}
return strings.Join(dcs, "."), nil
}
@@ -1879,6 +1879,136 @@ func TestBaseToSshPriKey(t *testing.T) {
}
}
func TestRepository_ldapCertIssuingCredentialLibrary_retrieveCredential(t *testing.T) {
// Create Vault server
v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS), WithVaultVersion("1.20.0"), WithDockerNetwork(true))
require.NotNil(t, v)
vc := v.client(t).cl
mounts, err := vc.Sys().ListMounts()
require.NoError(t, err)
require.NotEmpty(t, mounts)
// Create OpenLDAP server and mount Vault LDAP secrets engine.
_ = MountLdapServer(t, v)
// Create and setup Boundary DB
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
sche := scheduler.TestScheduler(t, conn, wrapper)
kms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
require.NoError(t, err)
require.NotNil(t, repo)
sec, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "ldap"}), WithTokenPeriod(time.Hour))
require.NotNil(t, sec)
require.NotEmpty(t, token)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), v.Addr, token, sec.Auth.Accessor)
tests := []struct {
name string
vaultPath string
expStaticUsername string
expDomain string
expDynamicCred bool
expErr bool
expErrMsg string
}{
{
name: "doesntExistStaticCred",
vaultPath: "ldap/static-cred/doesntexist",
expErr: true,
expErrMsg: "unknown role:",
},
{
name: "doesntExistDynamicCred",
vaultPath: "ldap/creds/doesntexist",
expErr: true,
expErrMsg: "vault secret is empty",
},
{
name: "staticCred",
vaultPath: "ldap/static-cred/einstein",
expStaticUsername: "einstein",
expDomain: "example.org",
},
{
name: "staticCredHierarchical",
vaultPath: "ldap/static-cred/myorg/myproject/newton",
expStaticUsername: "newton",
expDomain: "example.org",
},
{
name: "dynamicCred",
vaultPath: "ldap/creds/scientists",
expDynamicCred: true,
expDomain: "example.org",
},
{
name: "dynamicCredHierarchical",
vaultPath: "ldap/creds/myorg/myproject/scientists",
expDynamicCred: true,
expDomain: "example.org",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
lib, err := NewLdapCredentialLibrary(cs.GetPublicId(), tt.vaultPath)
require.NoError(t, err)
require.NotNil(t, lib)
lib.PublicId, err = newLdapCredentialLibraryId(t.Context())
require.NoError(t, err)
_, err = rw.DoTx(t.Context(), db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, iw db.Writer) error {
return iw.Create(t.Context(), lib)
},
)
require.NoError(t, err)
libs, err := repo.getIssueCredLibraries(t.Context(), []credential.Request{{
SourceId: lib.GetPublicId(),
}})
require.NoError(t, err)
require.Len(t, libs, 1)
cred, err := libs[0].retrieveCredential(t.Context(), "TestLdapRetrieveCredential")
if tt.expErr {
require.ErrorContains(t, err, tt.expErrMsg)
return
}
require.NoError(t, err)
require.IsType(t, &usrPassDomainCred{}, cred)
credUpd := cred.(*usrPassDomainCred)
require.NotEmpty(t, credUpd.baseCred)
require.NotEmpty(t, credUpd.getCredential())
require.EqualValues(t, libs[0], credUpd.lib)
require.NotEmpty(t, credUpd.secretData)
require.Contains(t, credUpd.secretData, "username")
require.Contains(t, credUpd.secretData, "password")
require.EqualValues(t, credUpd.secretData["password"], credUpd.Password())
require.EqualValues(t, tt.expDomain, credUpd.Domain())
if !tt.expDynamicCred {
require.EqualValues(t, credUpd.secretData["username"], credUpd.Username())
require.EqualValues(t, tt.expStaticUsername, credUpd.Username())
require.False(t, credUpd.GetIsRenewable())
} else {
require.EqualValues(t, credUpd.secretData["username"], credUpd.Username())
require.Contains(t, credUpd.Username(), "b_token-TestRepository-ldapCertIssuingCredentialLibrary-retrieveCredential_")
require.True(t, credUpd.GetIsRenewable())
}
})
}
}
func TestRepository_sshCertIssuingCredentialLibrary_retrieveCredential(t *testing.T) {
t.Parallel()
@@ -2093,3 +2223,83 @@ func TestRepository_sshCertIssuingCredentialLibrary_retrieveCredential(t *testin
})
}
}
func TestExtractDomainFromDn(t *testing.T) {
tests := []struct {
name string
inDn string
expDomain string
expErr bool
expErrMsgContains string
}{
{
name: "emptyString",
inDn: "",
expErr: true,
expErrMsgContains: "empty DN",
},
{
name: "notADn",
inDn: "a string that isn't really a DN",
expErr: true,
expErrMsgContains: "failed to parse DN:",
},
{
name: "validDn",
inDn: "dc=domain,dc=tld",
expDomain: "domain.tld",
},
{
name: "validDnSpaces",
inDn: "dc = domain , dc = tld",
expDomain: "domain.tld",
},
{
name: "validDnCaps",
inDn: "DC=DOMAIN,DC=TLD",
expDomain: "DOMAIN.TLD",
},
{
name: "validDnOid",
inDn: "0.9.2342.19200300.100.1.25=Domain,0.9.2342.19200300.100.1.25=Tld",
expDomain: "Domain.Tld",
},
{
name: "validDnOidMixed",
inDn: "dc=sub1,0.9.2342.19200300.100.1.25=Domain,dc=Tld",
expDomain: "sub1.Domain.Tld",
},
{
name: "validDnWithCn",
inDn: "CN=My User,CN=admin,DC=mycorp,DC=domain,DC=TLD",
expDomain: "mycorp.domain.TLD",
},
{
name: "validDnWithEscapeChars",
inDn: `CN=mycn,OU=HashiCorp\, an IBM Company,DC=domain,DC=TLD`,
expDomain: "domain.TLD",
},
{
name: "validDnWithEscapeChars2",
inDn: `CN=Before\0DAfter,OU=HashiCorp,DC=domain,DC=TLD`,
expDomain: "domain.TLD",
},
{
name: "validDnWithSubdomains",
inDn: `dc=foo,dc=bAR,dc=BAZ,dc=domain,dc=Tld`,
expDomain: "foo.bAR.BAZ.domain.Tld",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
domain, err := extractDomainFromDn(tt.inDn)
if tt.expErr {
require.ErrorContains(t, err, tt.expErrMsgContains)
require.Empty(t, domain)
return
}
require.NoError(t, err)
require.Equal(t, tt.expDomain, domain)
})
}
}
+10
View File
@@ -18,6 +18,7 @@ func init() {
globals.RegisterPrefixToResourceInfo(globals.VaultDynamicCredentialPrefix, resource.Credential, credential.Domain, Subtype)
globals.RegisterPrefixToResourceInfo(globals.VaultCredentialLibraryPrefix, resource.CredentialLibrary, credential.Domain, GenericLibrarySubtype)
globals.RegisterPrefixToResourceInfo(globals.VaultSshCertificateCredentialLibraryPrefix, resource.CredentialLibrary, credential.Domain, SSHCertificateLibrarySubtype)
globals.RegisterPrefixToResourceInfo(globals.VaultLdapCredentialLibraryPrefix, resource.CredentialLibrary, credential.Domain, LdapCredentialLibrarySubtype)
}
// PublicId prefixes for the resources in the vault package.
@@ -25,6 +26,7 @@ const (
Subtype = globals.Subtype("vault")
GenericLibrarySubtype = globals.Subtype("vault-generic")
SSHCertificateLibrarySubtype = globals.Subtype("vault-ssh-certificate")
LdapCredentialLibrarySubtype = globals.Subtype("vault-ldap")
)
func newCredentialStoreId(ctx context.Context) (string, error) {
@@ -58,3 +60,11 @@ func newSSHCertificateCredentialLibraryId(ctx context.Context) (string, error) {
}
return id, nil
}
func newLdapCredentialLibraryId(ctx context.Context) (string, error) {
id, err := db.NewPublicId(ctx, globals.VaultLdapCredentialLibraryPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, "vault.newLdapCredentialLibraryPrefix")
}
return id, nil
}
+119 -6
View File
@@ -194,8 +194,9 @@ delete from credential_vault_credential
select sum(reltuples::bigint) as estimate
from pg_class
where oid in (
'credential_vault_library'::regclass,
'credential_vault_ssh_cert_library'::regclass
'credential_vault_generic_library'::regclass,
'credential_vault_ssh_cert_library'::regclass,
'credential_vault_ldap_library'::regclass
)
`
@@ -209,7 +210,7 @@ with libraries as (
),
generic_libs as (
select *
from credential_vault_library
from credential_vault_generic_library
where public_id in (select public_id from libraries)
),
ssh_cert_libs as (
@@ -217,6 +218,11 @@ ssh_cert_libs as (
from credential_vault_ssh_cert_library
where public_id in (select public_id from libraries)
),
ldap_libs as (
select *
from credential_vault_ldap_library
where public_id in (select public_id from libraries)
),
final as (
select public_id,
store_id,
@@ -263,6 +269,29 @@ final as (
additional_valid_principals,
'ssh' as type
from ssh_cert_libs
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
vault_path,
credential_type,
null as http_method, -- Add to make union uniform
null as http_request_body, -- Add to make union uniform
null as username, -- Add to make union uniform
null as key_type, -- Add to make union uniform
null as key_bits, -- Add to make union uniform
null as ttl, -- Add to make union uniform
null as key_id, -- Add to make union uniform
null as critical_options, -- Add to make union uniform
null as extensions, -- Add to make union uniform
null as additional_valid_principals, -- Add to make union uniform
'ldap' as type
from ldap_libs
)
select *
from final
@@ -280,7 +309,7 @@ with libraries as (
),
generic_libs as (
select *
from credential_vault_library
from credential_vault_generic_library
where public_id in (select public_id from libraries)
),
ssh_cert_libs as (
@@ -288,6 +317,11 @@ ssh_cert_libs as (
from credential_vault_ssh_cert_library
where public_id in (select public_id from libraries)
),
ldap_libs as (
select *
from credential_vault_ldap_library
where public_id in (select public_id from libraries)
),
final as (
select public_id,
store_id,
@@ -334,6 +368,29 @@ final as (
additional_valid_principals,
'ssh' as type
from ssh_cert_libs
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
vault_path,
credential_type,
null as http_method, -- Add to make union uniform
null as http_request_body, -- Add to make union uniform
null as username, -- Add to make union uniform
null as key_type, -- Add to make union uniform
null as key_bits, -- Add to make union uniform
null as ttl, -- Add to make union uniform
null as key_id, -- Add to make union uniform
null as critical_options, -- Add to make union uniform
null as extensions, -- Add to make union uniform
null as additional_valid_principals, -- Add to make union uniform
'ldap' as type
from ldap_libs
)
select *
from final
@@ -351,7 +408,7 @@ with libraries as (
),
generic_libs as (
select *
from credential_vault_library
from credential_vault_generic_library
where public_id in (select public_id from libraries)
),
ssh_cert_libs as (
@@ -359,6 +416,11 @@ ssh_cert_libs as (
from credential_vault_ssh_cert_library
where public_id in (select public_id from libraries)
),
ldap_libs as (
select *
from credential_vault_ldap_library
where public_id in (select public_id from libraries)
),
final as (
select public_id,
store_id,
@@ -405,6 +467,29 @@ final as (
additional_valid_principals,
'ssh' as type
from ssh_cert_libs
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
vault_path,
credential_type,
null as http_method, -- Add to make union uniform
null as http_request_body, -- Add to make union uniform
null as username, -- Add to make union uniform
null as key_type, -- Add to make union uniform
null as key_bits, -- Add to make union uniform
null as ttl, -- Add to make union uniform
null as key_id, -- Add to make union uniform
null as critical_options, -- Add to make union uniform
null as extensions, -- Add to make union uniform
null as additional_valid_principals, -- Add to make union uniform
'ldap' as type
from ldap_libs
)
select *
from final
@@ -423,7 +508,7 @@ with libraries as (
),
generic_libs as (
select *
from credential_vault_library
from credential_vault_generic_library
where public_id in (select public_id from libraries)
),
ssh_cert_libs as (
@@ -431,6 +516,11 @@ ssh_cert_libs as (
from credential_vault_ssh_cert_library
where public_id in (select public_id from libraries)
),
ldap_libs as (
select *
from credential_vault_ldap_library
where public_id in (select public_id from libraries)
),
final as (
select public_id,
store_id,
@@ -477,6 +567,29 @@ final as (
additional_valid_principals,
'ssh' as type
from ssh_cert_libs
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
vault_path,
credential_type,
null as http_method, -- Add to make union uniform
null as http_request_body, -- Add to make union uniform
null as username, -- Add to make union uniform
null as key_type, -- Add to make union uniform
null as key_bits, -- Add to make union uniform
null as ttl, -- Add to make union uniform
null as key_id, -- Add to make union uniform
null as critical_options, -- Add to make union uniform
null as extensions, -- Add to make union uniform
null as additional_valid_principals, -- Add to make union uniform
'ldap' as type
from ldap_libs
)
select *
from final
@@ -421,7 +421,7 @@ func (pl *listLookupLibrary) toCredentialLibrary() *CredentialLibrary {
}
// TableName returns the table name for gorm.
func (*listLookupLibrary) TableName() string { return "credential_vault_library_list_lookup" }
func (*listLookupLibrary) TableName() string { return "credential_vault_generic_library_list_lookup" }
// GetPublicId returns the public id.
func (pl *listLookupLibrary) GetPublicId() string { return pl.PublicId }
@@ -0,0 +1,224 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/go-dbw"
)
// CreateLdapCredentialLibrary creates a new Vault LDAP credential library. It
// inserts the input domain object into the repository and returns the inserted
// domain object. No options are supported.
//
// The input domain object is not mutated, must contain a valid credential store
// id and must not contain a public id.
func (r *Repository) CreateLdapCredentialLibrary(ctx context.Context, projectId string, l *LdapCredentialLibrary, _ ...Option) (*LdapCredentialLibrary, error) {
const op = "vault.(Repository).CreateLdapCredentialLibrary"
if projectId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no project id")
}
if l == nil || l.LdapCredentialLibrary == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil input domain object")
}
if l.PublicId != "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty")
}
if l.StoreId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no store id")
}
if l.VaultPath == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no vault path")
}
l = l.clone()
if l.GetCredentialType() == "" {
l.LdapCredentialLibrary.CredentialType = string(globals.UsernamePasswordDomainCredentialType)
}
if l.GetCredentialType() != string(globals.UsernamePasswordDomainCredentialType) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid credential type")
}
id, err := newLdapCredentialLibraryId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
l.setId(id)
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var insertedLdapCredLib *LdapCredentialLibrary
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
insertedLdapCredLib = l.clone()
err = w.Create(ctx, insertedLdapCredLib,
db.WithOplog(oplogWrapper, insertedLdapCredLib.oplog(oplog.OpType_OP_TYPE_CREATE)),
)
if err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("in credential store: %s: name %s already exists", l.StoreId, l.Name))
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("credential store: %s", l.StoreId))
}
return insertedLdapCredLib, nil
}
// UpdateLdapCredentialLibrary updates an existing Vault LDAP credential library
// in the repository. It returns a domain object containing the updated state
// and the number of updated records. No options are supported.
//
// The input domain object is not mutated and must contain a valid public id.
func (r *Repository) UpdateLdapCredentialLibrary(ctx context.Context, projectId string, l *LdapCredentialLibrary, version uint32, fieldMaskPaths []string, _ ...Option) (*LdapCredentialLibrary, int, error) {
const op = "vault.(Repository).UpdateLdapCredentialLibrary"
if projectId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no project id")
}
if version == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
if l == nil || l.LdapCredentialLibrary == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "nil domain object")
}
if l.PublicId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
}
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
case strings.EqualFold(descriptionField, f):
case strings.EqualFold(vaultPathField, f):
default:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("%q field mask path is unsupported", f))
}
}
dbMask, nullFields := dbw.BuildUpdatePaths(
map[string]any{
nameField: l.Name,
descriptionField: l.Description,
vaultPathField: l.VaultPath,
}, fieldMaskPaths, nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var updatedCredLib *LdapCredentialLibrary
var rowsUpdated int
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(r db.Reader, w db.Writer) error {
updated, err := w.Update(ctx, l.clone(), dbMask, nullFields,
db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_UPDATE)),
db.WithVersion(&version),
)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update credential library"))
}
if updated > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 credential library would have been updated")
}
rowsUpdated = updated
updatedCredLib = allocLdapCredentialLibrary()
updatedCredLib.PublicId = l.GetPublicId()
err = r.LookupByPublicId(ctx, updatedCredLib)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve updated credential library"))
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("name %s already exists: %s", l.Name, l.PublicId))
}
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("failed to update credential library %s", l.PublicId))
}
return updatedCredLib, rowsUpdated, nil
}
// LookupLdapCredentialLibrary looks up an existing Vault LDAP credential
// library in the repository. It returns the domain object for the given public
// id, if it exists. Returns nil, nil if not found. No options are supported.
func (r *Repository) LookupLdapCredentialLibrary(ctx context.Context, publicId string, _ ...Option) (*LdapCredentialLibrary, error) {
const op = "vault.(Repository).LookupLdapCredentialLibrary"
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
l := allocLdapCredentialLibrary()
l.PublicId = publicId
err := r.reader.LookupByPublicId(ctx, l)
if err != nil {
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("lookup failed for id %s", publicId))
}
return l, nil
}
// DeleteLdapCredentialLibrary deletes the Vault LDAP credential library with
// the given public id from the repository. Returns the number of records
// deleted. No options are supported.
func (r *Repository) DeleteLdapCredentialLibrary(ctx context.Context, projectId string, publicId string, _ ...Option) (int, error) {
const op = "vault.(Repository).DeleteLdapCredentialLibrary"
if publicId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
if projectId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no project id")
}
l := allocLdapCredentialLibrary()
l.PublicId = publicId
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var rowsDeleted int
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
rowsDeleted, err = w.Delete(ctx, l, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_DELETE)))
if err == nil && rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 credential library would have been deleted")
}
return err
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("delete failed for id %s", l.PublicId))
}
return rowsDeleted, nil
}
@@ -0,0 +1,709 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package vault
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestCreateLdapCredentialLibrary(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
tests := []struct {
name string
inProjectId string
inDomainObj *LdapCredentialLibrary
expOut *LdapCredentialLibrary
expErr bool
expErrMsg string
}{
{
name: "noProjectId",
inProjectId: "",
inDomainObj: nil,
expErr: true,
expErrMsg: "no project id",
},
{
name: "nilDomainObject",
inProjectId: proj.GetPublicId(),
inDomainObj: nil,
expErr: true,
expErrMsg: "nil input domain object",
},
{
name: "nilStoreObject",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{LdapCredentialLibrary: nil},
expErr: true,
expErrMsg: "nil input domain object",
},
{
name: "emptyObject",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{LdapCredentialLibrary: &store.LdapCredentialLibrary{}},
expErr: true,
expErrMsg: "no store id",
},
{
name: "withPublicId",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
PublicId: "cvllt_3o2iuhOpZ",
StoreId: cs.GetPublicId(),
Name: "withPublicId",
Description: "withPublicId Subtest",
VaultPath: "ldap/creds/dynamic1",
},
},
expErr: true,
expErrMsg: "public id not empty",
},
{
name: "noStoreId",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: "",
Name: "noStoreId",
Description: "noStoreId Subtest",
VaultPath: "ldap/creds/dynamic1",
},
},
expErr: true,
expErrMsg: "no store id",
},
{
name: "noVaultPath",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "noVaultPath",
Description: "noVaultPath Subtest",
VaultPath: "",
},
},
expErr: true,
expErrMsg: "no vault path",
},
{
name: "invalidCredentialType",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "invalidCredentialType",
Description: "invalidCredentialType Subtest",
VaultPath: "ldap/creds/dynamic1",
CredentialType: "cred_type_that_doesnt_exist",
},
},
expErr: true,
expErrMsg: "invalid credential type",
},
{
name: "invalidVaultPath1",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "invalidVaultPath1",
Description: "invalidVaultPath1 Subtest",
VaultPath: "ldap/bad/some/test/path",
},
},
expErr: true,
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "invalidVaultPath2",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "invalidVaultPath2",
Description: "invalidVaultPath2 Subtest",
VaultPath: "ldap/static-credbad/some/test/path",
},
},
expErr: true,
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "invalidVaultPath3",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "invalidVaultPath3",
Description: "invalidVaultPath3 Subtest",
VaultPath: "ldap/badstatic-cred/some/test/path",
},
},
expErr: true,
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "invalidVaultPath4",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "invalidVaultPath4",
Description: "invalidVaultPath4 Subtest",
VaultPath: "ldap/credsbad/some/test/path",
},
},
expErr: true,
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "invalidVaultPath5",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "invalidVaultPath5",
Description: "invalidVaultPath5 Subtest",
VaultPath: "ldap/badcreds/some/test/path",
},
},
expErr: true,
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "validStaticCred1",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "validStaticCred1",
Description: "validStaticCred1 Subtest",
VaultPath: "ldap/static-cred/some/test/path",
},
},
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "validStaticCred1",
Description: "validStaticCred1 Subtest",
VaultPath: "ldap/static-cred/some/test/path",
},
},
},
{
name: "validStaticCred2",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "validStaticCred2",
Description: "validStaticCred2 Subtest",
VaultPath: "/ldap/static-cred/path",
},
},
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "validStaticCred2",
Description: "validStaticCred2 Subtest",
VaultPath: "/ldap/static-cred/path",
},
},
},
{
name: "validDynamicCreds1",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "validDynamicCreds1",
Description: "validDynamicCreds1 Subtest",
VaultPath: "ldap/creds/some/test/path",
},
},
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "validDynamicCreds1",
Description: "validDynamicCreds1 Subtest",
VaultPath: "ldap/creds/some/test/path",
},
},
},
{
name: "validDynamicCreds2",
inProjectId: proj.GetPublicId(),
inDomainObj: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "validDynamicCreds2",
Description: "validDynamicCreds2 Subtest",
VaultPath: "/ldap/creds/path",
},
},
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "validDynamicCreds2",
Description: "validDynamicCreds2 Subtest",
VaultPath: "/ldap/creds/path",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
require.NoError(t, err)
require.NotNil(t, repo)
ldapCredLib, err := repo.CreateLdapCredentialLibrary(t.Context(), tt.inProjectId, tt.inDomainObj)
if tt.expErr {
require.ErrorContains(t, err, tt.expErrMsg)
require.Nil(t, ldapCredLib)
return
}
require.NoError(t, err)
require.NotNil(t, ldapCredLib)
require.Contains(t, ldapCredLib.GetPublicId(), globals.VaultLdapCredentialLibraryPrefix)
require.NotEmpty(t, ldapCredLib.GetCreateTime())
require.NotEmpty(t, ldapCredLib.GetUpdateTime())
require.EqualValues(t, cs.GetPublicId(), ldapCredLib.GetStoreId())
require.EqualValues(t, 1, ldapCredLib.GetVersion())
require.EqualValues(t, string(globals.UsernamePasswordDomainCredentialType), ldapCredLib.GetCredentialType())
require.EqualValues(t, tt.expOut.GetName(), ldapCredLib.GetName())
require.EqualValues(t, tt.expOut.GetDescription(), ldapCredLib.GetDescription())
require.EqualValues(t, tt.expOut.GetVaultPath(), ldapCredLib.GetVaultPath())
})
}
}
func TestUpdateLdapCredentialLibrary(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
require.NoError(t, err)
require.NotNil(t, repo)
t.Run("nilDomainObject", func(t *testing.T) {
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), nil, 1, []string{})
require.ErrorContains(t, err, "nil domain object")
require.EqualValues(t, 0, updated)
require.Nil(t, l)
})
t.Run("nilStoreObject", func(t *testing.T) {
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(),
&LdapCredentialLibrary{LdapCredentialLibrary: nil}, 1, []string{})
require.ErrorContains(t, err, "nil domain object")
require.EqualValues(t, 0, updated)
require.Nil(t, l)
})
t.Run("emptyDomainObject", func(t *testing.T) {
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(),
&LdapCredentialLibrary{LdapCredentialLibrary: &store.LdapCredentialLibrary{}},
1, []string{},
)
require.ErrorContains(t, err, "missing public id")
require.EqualValues(t, 0, updated)
require.Nil(t, l)
})
t.Run("credLibDoesntExist", func(t *testing.T) {
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(),
&LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{PublicId: "doesnt_exist"},
}, 1, []string{"name", "description"},
)
require.ErrorContains(t, err, "record not found, search issue")
require.EqualValues(t, 0, updated)
require.Nil(t, l)
})
updateNameFn := func(name string) func(*LdapCredentialLibrary) *LdapCredentialLibrary {
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
lcl.Name = name
return lcl
}
}
updateDescriptionFn := func(description string) func(*LdapCredentialLibrary) *LdapCredentialLibrary {
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
lcl.Description = description
return lcl
}
}
updateVaultPathFn := func(vaultPath string) func(*LdapCredentialLibrary) *LdapCredentialLibrary {
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
lcl.VaultPath = vaultPath
return lcl
}
}
updateStoreIdFn := func() func(*LdapCredentialLibrary) *LdapCredentialLibrary {
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
lcl.StoreId = "this_is_immutable"
return lcl
}
}
tests := []struct {
name string
inProjectId string
inVersion uint32
inFieldMask []string
changeFns []func(*LdapCredentialLibrary) *LdapCredentialLibrary
expUpdate bool
expOut *LdapCredentialLibrary
expErr bool
expErrStr string
}{
{
name: "noProjectId",
inProjectId: "",
inVersion: 1,
inFieldMask: []string{"somepath"},
expErr: true,
expErrStr: "no project id",
},
{
name: "noVersion",
inProjectId: proj.GetPublicId(),
inVersion: 0,
inFieldMask: []string{"somepath"},
expErr: true,
expErrStr: "missing version",
},
{
name: "missingFieldMask",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{},
expErr: true,
expErrStr: "missing field mask",
},
{
name: "unsupportedFieldInFieldMask",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"name", "storeId"},
expErr: true,
expErrStr: `"storeId" field mask path is unsupported`,
},
{
name: "updateToInvalidVaultPath1",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"vaultPath"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateVaultPathFn("ldap/credbad/new"),
},
expErr: true,
expErrStr: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "updateToInvalidVaultPath2",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"vaultPath"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateVaultPathFn("ldap/static-credbad/new"),
},
expErr: true,
expErrStr: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "updateAndFieldMaskMismatch",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"name"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateDescriptionFn("updateAndFieldMaskMismatch Updated Description"),
},
expUpdate: false,
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "TestUpdateLdapCredentialLibrary/updateAndFieldMaskMismatch Original Name",
Description: "TestUpdateLdapCredentialLibrary/updateAndFieldMaskMismatch Original Description",
VaultPath: "ldap/creds/path",
},
},
},
{
name: "validUpdateName",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"name"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateNameFn("validUpdateName Subtest Updated Name"),
},
expUpdate: true,
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "validUpdateName Subtest Updated Name",
Description: "TestUpdateLdapCredentialLibrary/validUpdateName Original Description",
VaultPath: "ldap/creds/path",
},
},
},
{
name: "validUpdateDescription",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"description"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateDescriptionFn("validUpdateDescription Subtest Updated Description"),
},
expUpdate: true,
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "TestUpdateLdapCredentialLibrary/validUpdateDescription Original Name",
Description: "validUpdateDescription Subtest Updated Description",
VaultPath: "ldap/creds/path",
},
},
},
{
name: "validUpdateVaultPath",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"vaultPath"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateVaultPathFn("ldap/creds/updated/path/new"),
},
expUpdate: true,
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "TestUpdateLdapCredentialLibrary/validUpdateVaultPath Original Name",
Description: "TestUpdateLdapCredentialLibrary/validUpdateVaultPath Original Description",
VaultPath: "ldap/creds/updated/path/new",
},
},
},
{
name: "validUpdateMultipleFields",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"name", "description", "vaultPath"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateNameFn("validUpdateMultipleFields Updated Name"),
updateDescriptionFn("validUpdateMultipleFields Updated Description"),
updateVaultPathFn("ldap/static-cred/updated/path/new"),
},
expUpdate: true,
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "validUpdateMultipleFields Updated Name",
Description: "validUpdateMultipleFields Updated Description",
VaultPath: "ldap/static-cred/updated/path/new",
},
},
},
{
name: "validUpdateNameExtraChangesNotInFieldMask",
inProjectId: proj.GetPublicId(),
inVersion: 1,
inFieldMask: []string{"name"},
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
updateNameFn("validUpdateNameExtraChanges Updated Name"),
updateDescriptionFn("validUpdateNameExtraChanges Updated Description"),
updateVaultPathFn("ldap/creds/updated/path/new"),
updateStoreIdFn(),
},
expUpdate: true,
expOut: &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
Name: "validUpdateNameExtraChanges Updated Name",
Description: "TestUpdateLdapCredentialLibrary/validUpdateNameExtraChangesNotInFieldMask Original Description",
VaultPath: "ldap/creds/path",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
createdLib, err := repo.CreateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: t.Name() + " Original Name",
Description: t.Name() + " Original Description",
VaultPath: "ldap/creds/path",
},
})
require.NoError(t, err)
require.NotNil(t, createdLib)
inDomainObj := createdLib.clone()
for _, changeFn := range tt.changeFns {
inDomainObj = changeFn(inDomainObj)
}
updatedLdapLib, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(),
tt.inProjectId, inDomainObj, tt.inVersion, tt.inFieldMask,
)
if tt.expErr {
require.ErrorContains(t, err, tt.expErrStr)
require.Nil(t, updatedLdapLib)
require.EqualValues(t, 0, updated)
return
}
require.NoError(t, err)
require.NotNil(t, updatedLdapLib)
require.NotNil(t, updatedLdapLib.LdapCredentialLibrary)
if tt.expUpdate {
require.Greater(t, updatedLdapLib.GetUpdateTime().AsTime(), createdLib.GetUpdateTime().AsTime())
require.EqualValues(t, createdLib.GetVersion()+1, updatedLdapLib.GetVersion())
} else {
require.EqualValues(t, createdLib.GetUpdateTime().AsTime(), updatedLdapLib.GetUpdateTime().AsTime())
require.EqualValues(t, createdLib.GetVersion(), updatedLdapLib.GetVersion())
}
require.EqualValues(t, 1, updated)
require.EqualValues(t, createdLib.GetPublicId(), updatedLdapLib.GetPublicId())
require.EqualValues(t, createdLib.GetCreateTime().AsTime(), updatedLdapLib.GetCreateTime().AsTime())
require.EqualValues(t, createdLib.GetStoreId(), updatedLdapLib.GetStoreId())
require.EqualValues(t, createdLib.GetCredentialType(), updatedLdapLib.GetCredentialType())
require.EqualValues(t, tt.expOut.GetName(), updatedLdapLib.GetName())
require.EqualValues(t, tt.expOut.GetDescription(), updatedLdapLib.GetDescription())
require.EqualValues(t, tt.expOut.GetVaultPath(), updatedLdapLib.GetVaultPath())
})
}
}
func TestLookupLdapCredentialLibrary(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
require.NoError(t, err)
require.NotNil(t, repo)
t.Run("noPublicId", func(t *testing.T) {
l, err := repo.LookupLdapCredentialLibrary(t.Context(), "")
require.ErrorContains(t, err, "no public id")
require.Nil(t, l)
})
t.Run("credLibNotFound", func(t *testing.T) {
l, err := repo.LookupLdapCredentialLibrary(t.Context(), "alibthat_doesnt_exist")
require.NoError(t, err)
require.Nil(t, l)
})
t.Run("success", func(t *testing.T) {
createdLib, err := repo.CreateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "success",
Description: "success Subtest",
VaultPath: "ldap/creds/path",
},
})
require.NoError(t, err)
require.NotNil(t, createdLib)
lookupLib, err := repo.LookupLdapCredentialLibrary(t.Context(), createdLib.GetPublicId())
require.NoError(t, err)
require.Empty(t, cmp.Diff(createdLib, lookupLib, protocmp.Transform()))
})
}
func TestDeleteLdapCredentialLibrary(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
require.NoError(t, err)
require.NotNil(t, repo)
t.Run("noPublicId", func(t *testing.T) {
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), proj.GetPublicId(), "")
require.ErrorContains(t, err, "no public id")
require.EqualValues(t, 0, deleted)
})
t.Run("noProjectId", func(t *testing.T) {
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), "", "cvllt_doesntexist")
require.ErrorContains(t, err, "no project id")
require.EqualValues(t, 0, deleted)
})
t.Run("credLibDoesntExist", func(t *testing.T) {
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), proj.GetPublicId(), "cvllt_doesntexist")
require.NoError(t, err)
require.EqualValues(t, 0, deleted)
})
t.Run("success", func(t *testing.T) {
createdLib, err := repo.CreateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), &LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
StoreId: cs.GetPublicId(),
Name: "success",
Description: "success Subtest",
VaultPath: "ldap/creds/path",
},
})
require.NoError(t, err)
require.NotNil(t, createdLib)
lookupLib, err := repo.LookupLdapCredentialLibrary(t.Context(), createdLib.GetPublicId())
require.NoError(t, err)
require.Empty(t, cmp.Diff(createdLib, lookupLib, protocmp.Transform()))
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), proj.GetPublicId(), lookupLib.GetPublicId())
require.NoError(t, err)
require.EqualValues(t, 1, deleted)
lookupLib2, err := repo.LookupLdapCredentialLibrary(t.Context(), createdLib.GetPublicId())
require.NoError(t, err)
require.Nil(t, lookupLib2)
})
}
+171 -24
View File
@@ -1269,6 +1269,134 @@ func (x *SshPrivateKeyOverride) GetPrivateKeyPassphraseAttribute() string {
return ""
}
type LdapCredentialLibrary struct {
state protoimpl.MessageState `protogen:"open.v1"`
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
// name is optional. If set, it must be unique within project_id.
// @inject_tag: `gorm:"default:null"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
// description is optional.
// @inject_tag: `gorm:"default:null"`
Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"`
// store_id of the owning vault credential store.
// It must be set.
// @inject_tag: `gorm:"not_null"`
StoreId string `protobuf:"bytes,6,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"`
// version allows optimistic locking of the resource.
// @inject_tag: `gorm:"default:null"`
Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// vault_path is the path in Vault to request credentials from.
// It must be set.
// @inject_tag: `gorm:"not_null"`
VaultPath string `protobuf:"bytes,8,opt,name=vault_path,json=vaultPath,proto3" json:"vault_path,omitempty" gorm:"not_null"`
// credential_type is always ssh_certificate
// @inject_tag: `gorm:"default:null"`
CredentialType string `protobuf:"bytes,9,opt,name=credential_type,json=credentialType,proto3" json:"credential_type,omitempty" gorm:"default:null"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *LdapCredentialLibrary) Reset() {
*x = LdapCredentialLibrary{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *LdapCredentialLibrary) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LdapCredentialLibrary) ProtoMessage() {}
func (x *LdapCredentialLibrary) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LdapCredentialLibrary.ProtoReflect.Descriptor instead.
func (*LdapCredentialLibrary) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{9}
}
func (x *LdapCredentialLibrary) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *LdapCredentialLibrary) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *LdapCredentialLibrary) GetUpdateTime() *timestamp.Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
func (x *LdapCredentialLibrary) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *LdapCredentialLibrary) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *LdapCredentialLibrary) GetStoreId() string {
if x != nil {
return x.StoreId
}
return ""
}
func (x *LdapCredentialLibrary) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *LdapCredentialLibrary) GetVaultPath() string {
if x != nil {
return x.VaultPath
}
return ""
}
func (x *LdapCredentialLibrary) GetCredentialType() string {
if x != nil {
return x.CredentialType
}
return ""
}
var File_controller_storage_credential_vault_store_v1_vault_proto protoreflect.FileDescriptor
const file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = "" +
@@ -1424,7 +1552,23 @@ const file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = ""
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
"\x12username_attribute\x18\x02 \x01(\tR\x11usernameAttribute\x122\n" +
"\x15private_key_attribute\x18\x03 \x01(\tR\x13privateKeyAttribute\x12G\n" +
" private_key_passphrase_attribute\x18\x04 \x01(\tR\x1dprivateKeyPassphraseAttributeBEZCgithub.com/hashicorp/boundary/internal/credential/vault/store;storeb\x06proto3"
" private_key_passphrase_attribute\x18\x04 \x01(\tR\x1dprivateKeyPassphraseAttribute\"\xd5\x03\n" +
"\x15LdapCredentialLibrary\x12\x1b\n" +
"\tpublic_id\x18\x01 \x01(\tR\bpublicId\x12K\n" +
"\vcreate_time\x18\x02 \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"createTime\x12K\n" +
"\vupdate_time\x18\x03 \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"updateTime\x12$\n" +
"\x04name\x18\x04 \x01(\tB\x10\xc2\xdd)\f\n" +
"\x04Name\x12\x04nameR\x04name\x12@\n" +
"\vdescription\x18\x05 \x01(\tB\x1e\xc2\xdd)\x1a\n" +
"\vDescription\x12\vdescriptionR\vdescription\x12\x19\n" +
"\bstore_id\x18\x06 \x01(\tR\astoreId\x12\x18\n" +
"\aversion\x18\a \x01(\rR\aversion\x12?\n" +
"\n" +
"vault_path\x18\b \x01(\tB \xc2\xdd)\x1c\n" +
"\tVaultPath\x12\x0fattributes.pathR\tvaultPath\x12'\n" +
"\x0fcredential_type\x18\t \x01(\tR\x0ecredentialTypeBEZCgithub.com/hashicorp/boundary/internal/credential/vault/store;storeb\x06proto3"
var (
file_controller_storage_credential_vault_store_v1_vault_proto_rawDescOnce sync.Once
@@ -1438,7 +1582,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP()
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData
}
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []any{
(*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore
(*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token
@@ -1449,29 +1593,32 @@ var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []an
(*UsernamePasswordOverride)(nil), // 6: controller.storage.credential.vault.store.v1.UsernamePasswordOverride
(*UsernamePasswordDomainOverride)(nil), // 7: controller.storage.credential.vault.store.v1.UsernamePasswordDomainOverride
(*SshPrivateKeyOverride)(nil), // 8: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*timestamp.Timestamp)(nil), // 9: controller.storage.timestamp.v1.Timestamp
(*LdapCredentialLibrary)(nil), // 9: controller.storage.credential.vault.store.v1.LdapCredentialLibrary
(*timestamp.Timestamp)(nil), // 10: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = []int32{
9, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
15, // [15:15] is the sub-list for method output_type
15, // [15:15] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
15, // [15:15] is the sub-list for extension extendee
0, // [0:15] is the sub-list for field type_name
10, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 15: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 16: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
17, // [17:17] is the sub-list for method output_type
17, // [17:17] is the sub-list for method input_type
17, // [17:17] is the sub-list for extension type_name
17, // [17:17] is the sub-list for extension extendee
0, // [0:17] is the sub-list for field type_name
}
func init() { file_controller_storage_credential_vault_store_v1_vault_proto_init() }
@@ -1485,7 +1632,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc), len(file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc)),
NumEnums: 0,
NumMessages: 9,
NumMessages: 10,
NumExtensions: 0,
NumServices: 0,
},
+223 -8
View File
@@ -7,6 +7,8 @@
package vault
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net/http"
@@ -32,15 +34,18 @@ const DefaultVaultVersion = "1.15.1"
var (
vaultRepository = "hashicorp/vault"
postgresRepository = "postgres"
ldapRepository = "osixia/openldap"
)
func init() {
newVaultServer = gotNewServer
mountLdapServer = gotLdapServer
mountDatabase = gotMountDatabase
mirror := os.Getenv("DOCKER_MIRROR")
if mirror != "" {
vaultRepository = strings.Join([]string{mirror, vaultRepository}, "/")
ldapRepository = strings.Join([]string{mirror, ldapRepository}, "/")
postgresRepository = strings.Join([]string{mirror, postgresRepository}, "/")
}
}
@@ -217,20 +222,233 @@ func gotNewServer(t testing.TB, opt ...TestOption) *TestVaultServer {
return server
}
func gotLdapServer(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestLdapServer {
require.NotNil(t, v.pool)
require.NotNil(t, v.network, "Vault server must be created with docker network")
require.NotNil(t, v.vaultContainer, "vault container doesn't exist")
require.Nil(t, v.ldapContainer, "ldap container exists")
pool, ok := v.pool.(*dockertest.Pool)
require.True(t, ok)
nw, ok := v.network.(*dockertest.Network)
require.True(t, ok)
opts := getTestOpts(t, opt...)
if opts.ldapRootDomain == "" {
opts.ldapRootDomain = "example.org"
}
if opts.bindPass == "" {
opts.bindPass = "admin"
}
dcl := []string{}
for d := range strings.SplitSeq(opts.ldapRootDomain, ".") {
dcl = append(dcl, "dc="+d)
}
dc := strings.Join(dcl, ",")
dn := fmt.Sprintf("cn=admin,%s", dc)
server := &TestLdapServer{
BindDomain: opts.ldapRootDomain,
BindPass: opts.bindPass,
BindDn: dn,
}
userLdif := func(username, dc string) string {
template := `
dn: cn=%[1]s,%[2]s
objectClass: inetOrgPerson
cn: %[1]s
sn: %[1]s
uid: %[1]s
userPassword: password
`
template = fmt.Sprintf(template, username, dc)
// LDIF is whitespace sensitive, so remove the leading tab characters we
// have from having indented the template.
return strings.ReplaceAll(template, "\t", "")
}
userEinsteinLdif := userLdif("einstein", dc)
userNewtonLdif := userLdif("newton", dc)
groupScientistsLdifTemplate := `
dn: cn=scientists,%[1]s
objectClass: groupOfUniqueNames
cn: scientists
uniqueMember: cn=einstein,%[1]s
uniqueMember: cn=netwon,%[1]s
`
groupLdif := fmt.Sprintf(groupScientistsLdifTemplate, dc)
// LDIF is whitespace sensitive, so remove the leading tab characters we
// have from having indented the template.
groupLdif = strings.ReplaceAll(groupLdif, "\t", "")
dir, err := base62.Random(12)
require.NoError(t, err)
dir = path.Join("/tmp", dir)
outdir := path.Join(dir, "ldap")
require.NoError(t, os.Mkdir(dir, os.ModePerm))
require.NoError(t, os.Mkdir(outdir, os.ModePerm))
require.NoError(t, os.WriteFile(fmt.Sprintf("%s/%s", outdir, "userEinstein.ldif"), []byte(userEinsteinLdif), 0o666))
require.NoError(t, os.WriteFile(fmt.Sprintf("%s/%s", outdir, "userNewton.ldif"), []byte(userNewtonLdif), 0o666))
require.NoError(t, os.WriteFile(fmt.Sprintf("%s/%s", outdir, "group.ldif"), []byte(groupLdif), 0o666))
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: ldapRepository,
Tag: "latest",
Networks: []*dockertest.Network{nw},
Mounts: []string{fmt.Sprintf("%s:/tmp/ldap", outdir)},
Env: []string{
fmt.Sprintf("LDAP_DOMAIN=%s", server.BindDomain),
fmt.Sprintf("LDAP_ADMIN_PASSWORD=%s", server.BindPass),
},
})
require.NoError(t, err)
t.Cleanup(func() { cleanupResource(t, pool, resource) })
v.ldapContainer = resource
server.Url = fmt.Sprintf("ldap://%s", resource.GetIPInNetwork(nw))
// Wait until the container is healthy or timeout.
timeout := 10 * time.Second
ctx, cancel := context.WithTimeout(t.Context(), timeout)
t.Cleanup(cancel)
for loop := true; loop; {
select {
case <-ctx.Done():
t.Fatalf("LDAP server was not healthy after trying for %s", timeout)
default:
buf := bytes.NewBuffer(nil)
exitCode, _ := resource.Exec([]string{
"ldapwhoami", "-x", "-H", "ldap://localhost", "-D", server.BindDn, "-w", server.BindPass,
}, dockertest.ExecOptions{
StdOut: buf,
})
// ldapwhoami will output the caller's identity in the form of a DN.
// Because we're using the admin user here, we can just look for the
// BindDn in stdout.
if exitCode == 0 || strings.Contains(buf.String(), server.BindDn) {
loop = false
}
}
}
buf := bytes.NewBuffer(nil)
ebuf := bytes.NewBuffer(nil)
exitCode, err := resource.Exec([]string{"ldapadd", "-x", "-H", "ldap://localhost", "-D", server.BindDn, "-w", server.BindPass, "-f", "/tmp/ldap/userEinstein.ldif"}, dockertest.ExecOptions{
StdOut: buf,
StdErr: ebuf,
})
require.NoError(t, err)
require.Empty(t, ebuf)
require.Equal(t, 0, exitCode)
buf.Reset()
ebuf.Reset()
exitCode, err = resource.Exec([]string{"ldapadd", "-x", "-H", "ldap://localhost", "-D", server.BindDn, "-w", server.BindPass, "-f", "/tmp/ldap/userNewton.ldif"}, dockertest.ExecOptions{
StdOut: buf,
StdErr: ebuf,
})
require.NoError(t, err)
require.Empty(t, ebuf)
require.Equal(t, 0, exitCode)
buf.Reset()
ebuf.Reset()
exitCode, err = resource.Exec([]string{"ldapadd", "-x", "-H", "ldap://localhost", "-D", server.BindDn, "-w", server.BindPass, "-f", "/tmp/ldap/group.ldif"}, dockertest.ExecOptions{
StdOut: buf,
StdErr: ebuf,
})
require.NoError(t, err)
require.Empty(t, ebuf)
require.Equal(t, 0, exitCode)
// Mount and configure Vault LDAP secrets engine.
v.MountLdap(t, WithBindDn(server.BindDn), WithBindPass(server.BindPass), WithLdapUrl(server.Url))
vc := v.client(t).cl
mounts, err := vc.Sys().ListMounts()
require.NoError(t, err)
require.Contains(t, mounts, "ldap/")
// Add static roles that correspond to the LDAP server users.
_, err = vc.Logical().Write("ldap/static-role/einstein", map[string]any{
"dn": "cn=einstein,dc=example,dc=org",
"username": "einstein",
"rotation_period": "24h",
})
require.NoError(t, err)
_, err = vc.Logical().Write("ldap/static-role/myorg/myproject/newton", map[string]any{
"dn": "cn=newton,dc=example,dc=org",
"username": "newton",
"rotation_period": "24h",
})
require.NoError(t, err)
// Setup LDAP dynamic credentials.
ldifCreateTemplate := `
dn: cn={{.Username}},dc=example,dc=org
changetype: add
objectClass: inetOrgPerson
cn: {{.Username}}
sn: {{.Username}}
uid: {{.Username}}
userPassword:{{.Password}}
dn: cn=scientists,dc=example,dc=org
changetype: modify
add: uniqueMember
uniqueMember: cn={{.Username}},dc=example,dc=org
-`
ldifCreateTemplate = strings.ReplaceAll(ldifCreateTemplate, "\t", "")
ldifDeleteTemplate := `
dn: cn={{.Username}},dc=example,dc=org
changetype: delete
`
ldifDeleteTemplate = strings.ReplaceAll(ldifDeleteTemplate, "\t", "")
_, err = vc.Logical().Write("ldap/role/scientists", map[string]any{
"username_template": "b_{{.DisplayName}}_{{.RoleName}}_{{random 10}}_{{unix_time}}",
"creation_ldif": ldifCreateTemplate,
"deletion_ldif": ldifDeleteTemplate,
"rollback_ldif": ldifDeleteTemplate,
"default_ttl": "1h",
"max_ttl": "24h",
})
require.NoError(t, err)
_, err = vc.Logical().Write("ldap/role/myorg/myproject/scientists", map[string]any{
"username_template": "b_{{.DisplayName}}_{{.RoleName}}_{{random 10}}_{{unix_time}}",
"creation_ldif": ldifCreateTemplate,
"deletion_ldif": ldifDeleteTemplate,
"rollback_ldif": ldifDeleteTemplate,
"default_ttl": "1h",
"max_ttl": "24h",
})
require.NoError(t, err)
return server
}
func gotMountDatabase(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestDatabase {
require := require.New(t)
require.Nil(v.postgresContainer, "postgres container exists")
require.NotNil(v.pool)
require.NotNil(v.network, "Vault server must be created with docker network")
require.NotNil(v.vaultContainer, "vault container doesn't exist")
require.Nil(v.postgresContainer, "postgres container exists")
pool, ok := v.pool.(*dockertest.Pool)
require.True(ok)
network, ok := v.network.(*dockertest.Network)
nw, ok := v.network.(*dockertest.Network)
require.True(ok)
vaultContainer, ok := v.vaultContainer.(*dockertest.Resource)
require.True(ok)
dockerOptions := &dockertest.RunOptions{
Repository: postgresRepository,
Tag: globals.MinimumSupportedPostgresVersion,
Networks: []*dockertest.Network{network},
Networks: []*dockertest.Network{nw},
Env: []string{"POSTGRES_PASSWORD=password", "POSTGRES_DB=boundarytest"},
}
@@ -316,13 +534,10 @@ func gotMountDatabase(t testing.TB, v *TestVaultServer, opt ...TestOption) *Test
}
v.addPolicy(t, "database", pc)
vaultContainer, ok := v.vaultContainer.(*dockertest.Resource)
require.True(ok)
t.Log(vaultContainer.GetIPInNetwork(network))
t.Log(vaultContainer.GetIPInNetwork(nw))
// Configure PostgreSQL secrets engine
connUrl := fmt.Sprintf("postgresql://{{username}}:{{password}}@%s:5432/boundarytest?sslmode=disable", resource.GetIPInNetwork(network))
connUrl := fmt.Sprintf("postgresql://{{username}}:{{password}}@%s:5432/boundarytest?sslmode=disable", resource.GetIPInNetwork(nw))
t.Log(connUrl)
postgresConfPath := path.Join(mountPath, "config/postgresql")
+191 -1
View File
@@ -180,6 +180,44 @@ func TestSSHCertificateCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping
return libs
}
// TestLdapCredentialLibraries creates `count` number of Vault LDAP credential
// libraries in the provided db with the provided credential store. If any
// errors are encountered, the test will fail.
func TestLdapCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping.Wrapper, storeId string, count int) []*LdapCredentialLibrary {
t.Helper()
w := db.New(conn)
var libs []*LdapCredentialLibrary
for i := range count {
var staticOrDynamic string
switch mrand.Intn(2) {
case 0:
staticOrDynamic = "static-cred"
case 1:
staticOrDynamic = "creds"
}
lib, err := NewLdapCredentialLibrary(storeId, fmt.Sprintf("ldap/%s/role-%d", staticOrDynamic, i))
require.NoError(t, err)
require.NotNil(t, lib)
libId, err := newLdapCredentialLibraryId(t.Context())
require.NoError(t, err)
require.NotEmpty(t, libId)
lib.PublicId = libId
_, err = w.DoTx(t.Context(), db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
return w.Create(t.Context(), lib)
},
)
require.NoError(t, err)
libs = append(libs, lib)
}
return libs
}
// TestCredentials creates count number of vault credentials in the provided DB with
// the provided library id and session id. If any errors are encountered
// during the creation of the credentials, the test will fail.
@@ -532,6 +570,10 @@ type testOptions struct {
clientKey *ecdsa.PrivateKey
vaultVersion string
serverCertHostNames []string
bindDn string
bindPass string
ldapUrl string
ldapRootDomain string
}
func getDefaultTestOptions(t testing.TB) testOptions {
@@ -666,6 +708,40 @@ func WithAllowedExtension(e string) TestOption {
}
}
// WithBindDn sets the distinguished name to be used by the Vault LDAP secrets
// engine.
func WithBindDn(dn string) TestOption {
return func(t testing.TB, o *testOptions) {
t.Helper()
o.bindDn = dn
}
}
// WithBindPass sets the password to be used by the Vault LDAP secrets engine.
// It is also used to set the admin user password in the LDAP server.
func WithBindPass(p string) TestOption {
return func(t testing.TB, o *testOptions) {
t.Helper()
o.bindPass = p
}
}
// WithLdapUrl sets the URL to be used by the Vault LDAP secrets engine.
func WithLdapUrl(u string) TestOption {
return func(t testing.TB, o *testOptions) {
t.Helper()
o.ldapUrl = u
}
}
// WithLdapRootDomain controls the LDAP server's root domain.
func WithLdapRootDomain(d string) TestOption {
return func(t testing.TB, o *testOptions) {
t.Helper()
o.ldapRootDomain = d
}
}
// TestOrphanToken sets the token orphan option to b.
// The orphan option is true by default.
func TestOrphanToken(b bool) TestOption {
@@ -916,6 +992,81 @@ func (v *TestVaultServer) MountSSH(t testing.TB, opt ...TestOption) *vault.Secre
return s
}
// MountLdap mounts the Vault LDAP secrets engine on a Vault instance and
// initializes it.
//
// Supported options include:
// - WithTestMountPath: Sets the mount path. Defaults to "ldap".
// - WithBindDn: Sets the BindDN to be used by the Vault LDAP secrets engine.
// Defaults to "cn=admin,dc=example,dc=org".
// - WithBindPass: Sets the password to be used by the Vault LDAP secrets
// engine. Defaults to "admin".
// - WithLdapUrl: Sets the URL of the LDAP server to be used by the Vault LDAP
// secrets engine. Defaults to "ldap://ldap".
//
// MountLdap also adds a Vault policy named 'ldap' to the standard set of
// policies attached to tokens created with v.CreateToken. The policy is defined
// as:
//
// path "<ldapMountPath>/*" {
// capabilities = ["create", "read", "update", "delete", "list"]
// }
func (v *TestVaultServer) MountLdap(t testing.TB, opt ...TestOption) {
require := require.New(t)
opts := getTestOpts(t, opt...)
vc := v.client(t).cl
maxTTL := 24 * time.Hour
if t, ok := t.(*testing.T); ok {
if deadline, ok := t.Deadline(); ok {
maxTTL = time.Until(deadline) * 2
}
}
defaultTTL := maxTTL / 2
mountInput := &vault.MountInput{
Type: "ldap",
Description: t.Name(),
Config: vault.MountConfigInput{
DefaultLeaseTTL: defaultTTL.String(),
MaxLeaseTTL: maxTTL.String(),
},
}
mountPath := opts.mountPath
if mountPath == "" {
mountPath = "ldap/"
}
require.True(mountPath[len(mountPath)-1] == '/', "mount path must end with a slash")
require.NoError(vc.Sys().Mount(mountPath, mountInput))
policyPath := fmt.Sprintf("%s*", mountPath)
pc := pathCapabilities{
policyPath: createCapability | readCapability | updateCapability | deleteCapability | listCapability,
}
v.addPolicy(t, "ldap", pc)
// Create and write LDAP server config
bindDn := opts.bindDn
if bindDn == "" {
bindDn = "cn=admin,dc=example,dc=org"
}
bindPass := opts.bindPass
if bindPass == "" {
bindPass = "admin"
}
url := opts.ldapUrl
if url == "" {
url = "ldap://ldap"
}
ldapConfig := map[string]any{
"binddn": bindDn,
"bindpass": bindPass,
"url": url,
}
_, err := vc.Logical().Write(path.Join(mountPath, "config"), ldapConfig)
require.NoError(err)
}
// MountPKI mounts the Vault PKI secret engine and initializes it by
// generating a root certificate authority and creating a default role on
// the mount. The root CA is returned.
@@ -1038,9 +1189,12 @@ type TestVaultServer struct {
serverCertBundle *testCertBundle
clientCertBundle *testCertBundle
// Use any here instead of *dockertest.[Pool|Network|Resource] to avoid
// problems with dockertest not building for some netbsd architectures.
pool any
vaultContainer any
network any
vaultContainer any
ldapContainer any
postgresContainer any
}
@@ -1115,3 +1269,39 @@ func (d *TestDatabase) ValidateCredential(t testing.TB, s *vault.Secret) error {
}
return nil
}
// TestLdapServer is returned from MountLdapServer and contains information
// about how an LDAP server was setup. It is primarily used to test
// Boundary <> Vault <> LDAP Server interactions.
type TestLdapServer struct {
BindDomain string
BindPass string
BindDn string
Url string
}
// MountLdapServer creates an LDAP server as a Docker container, within a
// running Vault's Docker network. Additionally, it mounts and configures the
// LDAP secrets engine in the passed in Vault instance.
//
// Supported options:
// - WithBindPass. Controls the LDAP server's admin password. Defaults to
// "admin".
// - WithLdapRootDomain. Controls the LDAP server's root domain. Defaults to
// "example.org".
//
// Some roles are added to the LDAP server as well as configured within Vault:
// - scientists: A group of unique members: cn=scientists,dc=example,dc=org
// - einstein: A user belonging to the scientists group: cn=einstein,dc=example,dc=org
// - newton: Another user beloging to the scientists group: cn=newton,dc=example,dc=org
//
// Note that the domain components (dc) will change accordingly if WithDomain is
// passed in.
//
// Dynamic roles are also setup using LDIF templates. These roles trigger Vault
// to create users on-demand and manage their lifecycle. All dynamic users are
// added to the "scientists" group.
func MountLdapServer(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestLdapServer {
t.Helper()
return mountLdapServer(t, v, opt...)
}
@@ -60,6 +60,7 @@ const (
var (
maskManager handlers.MaskManager
sshCertMaskManager handlers.MaskManager
ldapMaskManager handlers.MaskManager
// IdActions contains the set of actions that can be performed on
// individual resources
@@ -107,6 +108,13 @@ func init() {
); err != nil {
panic(err)
}
if ldapMaskManager, err = handlers.NewMaskManager(
context.Background(),
handlers.MaskDestination{&store.LdapCredentialLibrary{}},
handlers.MaskSource{&pb.CredentialLibrary{}, &pb.VaultLdapCredentialLibraryAttributes{}},
); err != nil {
panic(err)
}
// TODO: refactor to remove IdActionsMap and CollectionActions package variables
action.RegisterResource(resource.CredentialLibrary, IdActions, CollectionActions)
@@ -366,19 +374,27 @@ func (s Service) UpdateCredentialLibrary(ctx context.Context, req *pbs.UpdateCre
var currentCredentialType globals.CredentialType
var mo vault.MappingOverride
switch globals.ResourceInfoFromPrefix(req.GetId()).Subtype {
case vault.SSHCertificateLibrarySubtype:
cur, err := repo.LookupSSHCertificateCredentialLibrary(ctx, req.Id)
if err != nil {
return nil, err
}
currentCredentialType = globals.CredentialType(cur.GetCredentialType())
default:
case vault.GenericLibrarySubtype:
cur, err := repo.LookupCredentialLibrary(ctx, req.Id)
if err != nil {
return nil, err
}
currentCredentialType = globals.CredentialType(cur.GetCredentialType())
mo = cur.MappingOverride
case vault.SSHCertificateLibrarySubtype:
cur, err := repo.LookupSSHCertificateCredentialLibrary(ctx, req.Id)
if err != nil {
return nil, err
}
currentCredentialType = globals.CredentialType(cur.GetCredentialType())
case vault.LdapCredentialLibrarySubtype:
cur, err := repo.LookupLdapCredentialLibrary(ctx, req.GetId())
if err != nil {
return nil, err
}
currentCredentialType = globals.CredentialType(cur.GetCredentialType())
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, "unknown vault credential library subtype")
}
if err := validateUpdateRequest(req, currentCredentialType); err != nil {
@@ -456,6 +472,15 @@ func (s Service) getFromRepo(ctx context.Context, id string) (credential.Library
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("ssh certificate credential library %q not found", id))
}
return cs, err
case vault.LdapCredentialLibrarySubtype:
cs, err := repo.LookupLdapCredentialLibrary(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
}
if cs == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("ldapcredential library %q not found", id))
}
return cs, err
}
return nil, errors.New(ctx, errors.InvalidParameter, op, "unrecognized credential library subtype")
}
@@ -464,6 +489,23 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Cred
const op = "credentiallibraries.(Service).createInRepo"
var out credential.Library
switch item.GetType() {
case vault.GenericLibrarySubtype.String():
cl, err := toStorageVaultLibrary(ctx, item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
rl, err := repo.CreateCredentialLibrary(ctx, scopeId, cl)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential library"))
}
if rl == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential library but no error returned from repository.")
}
out = rl
case vault.SSHCertificateLibrarySubtype.String():
cl, err := toStorageVaultSSHCertificateLibrary(ctx, item.GetCredentialStoreId(), item)
if err != nil {
@@ -481,8 +523,8 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Cred
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create ssh certificate credential library but no error returned from repository.")
}
out = rl
default:
cl, err := toStorageVaultLibrary(ctx, item.GetCredentialStoreId(), item)
case vault.LdapCredentialLibrarySubtype.String():
cl, err := toStorageVaultLdapLibrary(ctx, item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
@@ -490,14 +532,16 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Cred
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
rl, err := repo.CreateCredentialLibrary(ctx, scopeId, cl)
rl, err := repo.CreateLdapCredentialLibrary(ctx, scopeId, cl)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential library"))
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create ldap credential library"))
}
if rl == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential library but no error returned from repository.")
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create ldap credential library but no error returned from repository.")
}
out = rl
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Unknown vault credential library subtype %q", item.GetType())
}
return out, nil
}
@@ -534,6 +578,23 @@ func (s Service) updateInRepo(
return nil, errors.Wrap(ctx, err, op)
}
switch globals.ResourceInfoFromPrefix(id).Subtype {
case vault.GenericLibrarySubtype:
dbMasks = append(dbMasks, maskManager.Translate(masks)...)
if len(dbMasks) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
cl, err := toStorageVaultLibrary(ctx, item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cl.PublicId = id
out, rowsUpdated, err = repo.UpdateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential Library %q doesn't exist or incorrect version provided.", id)
}
case vault.SSHCertificateLibrarySubtype:
dbMasks = append(dbMasks, sshCertMaskManager.Translate(masks)...)
if getMapUpdate(criticalOptionsField, masks) {
@@ -557,23 +618,25 @@ func (s Service) updateInRepo(
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential Library %q doesn't exist or incorrect version provided.", id)
}
default:
dbMasks = append(dbMasks, maskManager.Translate(masks)...)
case vault.LdapCredentialLibrarySubtype:
dbMasks = append(dbMasks, ldapMaskManager.Translate(masks)...)
if len(dbMasks) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
cl, err := toStorageVaultLibrary(ctx, item.GetCredentialStoreId(), item)
cl, err := toStorageVaultLdapLibrary(ctx, item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cl.PublicId = id
out, rowsUpdated, err = repo.UpdateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMasks)
out, rowsUpdated, err = repo.UpdateLdapCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential Library %q doesn't exist or incorrect version provided.", id)
}
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Unknown vault credential library subtype %q", globals.ResourceInfoFromPrefix(id).Subtype)
}
return out, nil
}
@@ -586,10 +649,14 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
}
rows := 0
switch globals.ResourceInfoFromPrefix(id).Subtype {
case vault.GenericLibrarySubtype:
rows, err = repo.DeleteCredentialLibrary(ctx, scopeId, id)
case vault.SSHCertificateLibrarySubtype:
rows, err = repo.DeleteSSHCertificateCredentialLibrary(ctx, scopeId, id)
case vault.LdapCredentialLibrarySubtype:
rows, err = repo.DeleteLdapCredentialLibrary(ctx, scopeId, id)
default:
rows, err = repo.DeleteCredentialLibrary(ctx, scopeId, id)
return false, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Unknown vault credential library subtype %q", globals.ResourceInfoFromPrefix(id).Subtype)
}
if err != nil {
if errors.IsNotFoundError(err) {
@@ -640,6 +707,17 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type, isRec
return res
}
parentId = cl.GetStoreId()
case vault.LdapCredentialLibrarySubtype:
cl, err := repo.LookupLdapCredentialLibrary(ctx, id)
if err != nil {
res.Error = err
return res
}
if cl == nil {
res.Error = handlers.NotFoundError()
return res
}
parentId = cl.GetStoreId()
default:
res.Error = errors.New(ctx, errors.InvalidParameter, op, "unrecognized credential library subtype from id")
return res
@@ -854,7 +932,21 @@ func toProto(ctx context.Context, in credential.Library, opt ...handlers.Option)
VaultSshCertificateCredentialLibraryAttributes: attrs,
}
}
case vault.LdapCredentialLibrarySubtype:
vaultIn, ok := in.(*vault.LdapCredentialLibrary)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "unable to cast to vault ldap credential library")
}
if outputFields.Has(globals.CredentialTypeField) {
out.CredentialType = vaultIn.GetCredentialType()
}
if outputFields.Has(globals.AttributesField) {
out.Attrs = &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(vaultIn.GetVaultPath()),
},
}
}
}
return &out, nil
}
@@ -992,20 +1084,33 @@ func toStorageVaultSSHCertificateLibrary(ctx context.Context, storeId string, in
return cs, err
}
func toStorageVaultLdapLibrary(ctx context.Context, storeId string, in *pb.CredentialLibrary) (*vault.LdapCredentialLibrary, error) {
const op = "credentiallibraries.toStorageVaultLdapLibrary"
var opts []vault.Option
if in.GetName() != nil {
opts = append(opts, vault.WithName(in.GetName().GetValue()))
}
if in.GetDescription() != nil {
opts = append(opts, vault.WithDescription(in.GetDescription().GetValue()))
}
attrs := in.GetVaultLdapCredentialLibraryAttributes()
cs, err := vault.NewLdapCredentialLibrary(storeId, attrs.GetPath().GetValue(), opts...)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build credential library"))
}
return cs, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
// - The path passed in is correctly formatted
// - All required parameters are set
// - There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetCredentialLibraryRequest) error {
prefix := ""
switch globals.ResourceInfoFromPrefix(req.GetId()).Subtype {
case vault.SSHCertificateLibrarySubtype:
prefix = globals.VaultSshCertificateCredentialLibraryPrefix
default:
prefix = globals.VaultCredentialLibraryPrefix
}
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, prefix)
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, globals.VaultCredentialLibraryPrefix, globals.VaultSshCertificateCredentialLibraryPrefix, globals.VaultLdapCredentialLibraryPrefix)
}
func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
@@ -1048,8 +1153,9 @@ func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
}
if t != vault.GenericLibrarySubtype.String() &&
t != vault.SSHCertificateLibrarySubtype.String() {
badFields[globals.CredentialStoreIdField] = fmt.Sprintf("Type must be a vault subtype %q or %q", vault.GenericLibrarySubtype.String(), vault.SSHCertificateLibrarySubtype.String())
t != vault.SSHCertificateLibrarySubtype.String() &&
t != vault.LdapCredentialLibrarySubtype.String() {
badFields[globals.CredentialStoreIdField] = fmt.Sprintf("Type must be a vault subtype %q, %q or %q", vault.GenericLibrarySubtype.String(), vault.SSHCertificateLibrarySubtype.String(), vault.LdapCredentialLibrarySubtype.String())
}
switch req.GetItem().GetType() {
@@ -1111,6 +1217,17 @@ func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
badFields[keyTypeField] = "If set, value must be 'ed25519', 'ecdsa', or 'rsa'."
}
validateKeyBits(badFields, attrs.GetKeyBits().GetValue(), attrs.GetKeyType().GetValue())
case vault.LdapCredentialLibrarySubtype.String():
if req.GetItem().GetCredentialType() != "" {
badFields[globals.CredentialTypeField] = "This field is read only and cannot be set."
}
attrs := req.GetItem().GetVaultLdapCredentialLibraryAttributes()
if attrs == nil {
badFields[attributesPathField] = "This is a required field."
}
if attrs.GetPath().GetValue() == "" {
badFields[vaultPathField] = "This is a required field."
}
}
default:
badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id."
@@ -1120,17 +1237,9 @@ func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
}
func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest, currentCredentialType globals.CredentialType) error {
prefix := ""
st := globals.ResourceInfoFromPrefix(req.GetId()).Subtype
switch st {
case vault.GenericLibrarySubtype:
prefix = globals.VaultCredentialLibraryPrefix
case vault.SSHCertificateLibrarySubtype:
prefix = globals.VaultSshCertificateCredentialLibraryPrefix
}
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
switch st {
switch globals.ResourceInfoFromPrefix(req.GetId()).Subtype {
case vault.GenericLibrarySubtype:
if req.GetItem().GetType() != "" && req.GetItem().GetType() != vault.GenericLibrarySubtype.String() {
badFields[globals.TypeField] = "Cannot modify resource type."
@@ -1175,13 +1284,27 @@ func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest, currentCrede
}
validateKeyBits(badFields, attrs.GetKeyBits().GetValue(), attrs.GetKeyType().GetValue())
}
case vault.LdapCredentialLibrarySubtype:
if req.GetItem().GetType() != "" && req.GetItem().GetType() != vault.LdapCredentialLibrarySubtype.String() {
badFields[globals.TypeField] = "Cannot modify resource type."
}
if req.GetItem().GetCredentialType() != "" && req.GetItem().GetCredentialType() != string(currentCredentialType) {
badFields[globals.CredentialTypeField] = "Cannot modify credential type."
}
attrs := req.GetItem().GetVaultLdapCredentialLibraryAttributes()
if attrs != nil {
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), vaultPathField) && attrs.GetPath().GetValue() == "" {
badFields[vaultPathField] = "This is a required field and cannot be set to empty."
}
}
}
return badFields
}, prefix)
}, globals.VaultCredentialLibraryPrefix, globals.VaultSshCertificateCredentialLibraryPrefix, globals.VaultLdapCredentialLibraryPrefix)
}
func validateDeleteRequest(req *pbs.DeleteCredentialLibraryRequest) error {
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, globals.VaultCredentialLibraryPrefix, globals.VaultSshCertificateCredentialLibraryPrefix)
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, globals.VaultCredentialLibraryPrefix, globals.VaultSshCertificateCredentialLibraryPrefix, globals.VaultLdapCredentialLibraryPrefix)
}
func validateListRequest(ctx context.Context, req *pbs.ListCredentialLibrariesRequest) error {
@@ -14,6 +14,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
@@ -86,6 +87,25 @@ func sshCredentialLibraryToProto(credLib *vault.SSHCertificateCredentialLibrary,
}
}
func ldapCredentialLibraryToProto(credLib *vault.LdapCredentialLibrary, project *iam.Scope) *pb.CredentialLibrary {
return &pb.CredentialLibrary{
Id: credLib.GetPublicId(),
CredentialStoreId: credLib.GetStoreId(),
CredentialType: credLib.GetCredentialType(),
Scope: &scopepb.ScopeInfo{Id: project.GetPublicId(), Type: scope.Project.String(), ParentScopeId: project.GetParentId()},
CreatedTime: credLib.GetCreateTime().GetTimestamp(),
UpdatedTime: credLib.GetUpdateTime().GetTimestamp(),
Version: credLib.GetVersion(),
Type: vault.LdapCredentialLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(credLib.GetVaultPath()),
},
},
}
}
func TestList(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
@@ -114,6 +134,9 @@ func TestList(t *testing.T) {
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) {
wantLibraries = append(wantLibraries, sshCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) {
wantLibraries = append(wantLibraries, ldapCredentialLibraryToProto(l, prj))
}
cases := []struct {
name string
@@ -131,7 +154,7 @@ func TestList(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 20,
EstItemCount: 30,
},
anonRes: &pbs.ListCredentialLibrariesResponse{
Items: wantLibraries,
@@ -139,7 +162,7 @@ func TestList(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 20,
EstItemCount: 30,
},
},
{
@@ -264,16 +287,20 @@ func TestList_Attributes(t *testing.T) {
_, prj := iam.TestScopes(t, iamRepo)
ts := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
storeGeneric, storeSSHCertificate := ts[0], ts[1]
ts := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 3)
storeGeneric, storeSSHCertificate, storeLdap := ts[0], ts[1], ts[2]
var wantLibrariesGeneric []*pb.CredentialLibrary
var wantLibrariesSSHCertificate []*pb.CredentialLibrary
var wantLibrariesLdap []*pb.CredentialLibrary
for _, l := range vault.TestCredentialLibraries(t, conn, wrapper, storeGeneric.GetPublicId(), globals.UnspecifiedCredentialType, 5) {
wantLibrariesGeneric = append(wantLibrariesGeneric, vaultCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, storeSSHCertificate.GetPublicId(), 10) {
wantLibrariesSSHCertificate = append(wantLibrariesSSHCertificate, sshCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, storeLdap.GetPublicId(), 10) {
wantLibrariesLdap = append(wantLibrariesLdap, ldapCredentialLibraryToProto(l, prj))
}
cases := []struct {
name string
@@ -318,6 +345,24 @@ func TestList_Attributes(t *testing.T) {
RemovedIds: nil,
}, // anonymous user does not have access to attributes
},
{
name: "Filter on Attribute Ldap Library",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: storeLdap.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/path"==%q`, wantLibrariesLdap[2].GetVaultLdapCredentialLibraryAttributes().GetPath().Value)},
res: &pbs.ListCredentialLibrariesResponse{
Items: wantLibrariesLdap[2:3],
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 1,
},
anonRes: &pbs.ListCredentialLibrariesResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
}, // anonymous user does not have access to attributes
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -586,6 +631,51 @@ func TestCreate(t *testing.T) {
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "No Vault LDAP path",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.LdapCredentialLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(""),
},
},
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid Vault LDAP credential type",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.LdapCredentialLibrarySubtype.String(),
CredentialType: string(globals.SshPrivateKeyCredentialType),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/invalid/path"),
},
},
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid credential library type",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: "bla-invalid",
CredentialType: "bla-invalid",
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Using POST method",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
@@ -1103,6 +1193,40 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "Create a valid Vault LDAP Credential library",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.LdapCredentialLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/hi"),
},
},
},
},
idPrefix: globals.VaultLdapCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultLdapCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: vault.LdapCredentialLibrarySubtype.String(),
CredentialType: string(globals.UsernamePasswordDomainCredentialType),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/hi"),
},
},
AuthorizedActions: testAuthorizedActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -1217,6 +1341,13 @@ func TestGet(t *testing.T) {
sshCertLib, err := repo.CreateSSHCertificateCredentialLibrary(context.Background(), prj.GetPublicId(), lib2)
require.NoError(t, err)
libLdap1, err := vault.NewLdapCredentialLibrary(store.GetPublicId(), "ldap/creds/test")
require.NoError(t, err)
require.NotNil(t, libLdap1)
libLdap1, err = repo.CreateLdapCredentialLibrary(t.Context(), prj.GetPublicId(), libLdap1)
require.NoError(t, err)
require.NotNil(t, libLdap1)
cases := []struct {
name string
id string
@@ -1367,6 +1498,28 @@ func TestGet(t *testing.T) {
},
},
},
{
name: "success-ldap",
id: libLdap1.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: libLdap1.GetPublicId(),
CredentialStoreId: libLdap1.GetStoreId(),
CredentialType: libLdap1.GetCredentialType(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.LdapCredentialLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: libLdap1.CreateTime.GetTimestamp(),
UpdatedTime: libLdap1.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(libLdap1.GetVaultPath()),
},
},
},
},
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix),
@@ -1429,6 +1582,7 @@ func TestDelete(t *testing.T) {
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), globals.UnspecifiedCredentialType, 1)[0]
vl2 := vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
vl3 := vault.TestLdapCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
s, err := NewService(ctx, iamRepoFn, repoFn, 1000)
require.NoError(t, err)
cases := []struct {
@@ -1445,6 +1599,10 @@ func TestDelete(t *testing.T) {
name: "success-ssh-cert",
id: vl2.GetPublicId(),
},
{
name: "success-ldap",
id: vl3.GetPublicId(),
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix),
@@ -3244,6 +3402,287 @@ func TestUpdate_SSHCertificateCredentialLibrary(t *testing.T) {
}
}
func TestUpdate_LdapCredentialLibrary(t *testing.T) {
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(testCtx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
s, err := NewService(testCtx, iamRepoFn, repoFn, 1000)
require.NoError(t, err)
cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)
store := cs[0]
freshLibrary := func(opt ...vault.Option) (*vault.LdapCredentialLibrary, func()) {
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewLdapCredentialLibrary(store.GetPublicId(), "ldap/creds/foo", opt...)
require.NoError(t, err)
vl, err := repo.CreateLdapCredentialLibrary(ctx, prj.GetPublicId(), lib)
require.NoError(t, err)
clean := func() {
_, err := s.DeleteCredentialLibrary(ctx, &pbs.DeleteCredentialLibraryRequest{Id: vl.GetPublicId()})
require.NoError(t, err)
}
return vl, clean
}
tests := []struct {
name string
req *pbs.UpdateCredentialLibraryRequest
res func(*pb.CredentialLibrary) *pb.CredentialLibrary
wantErr bool
wantErrStatus int
wantErrStr string
}{
{
name: "updateNonExistentField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"doesntExist"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updateNonExistentField_test"),
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "No valid fields included in the update mask.",
},
{
name: "updateImmutableField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"storeId"}},
Item: &pb.CredentialLibrary{
CredentialStoreId: "immutable",
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "No valid fields included in the update mask.",
},
{
name: "updateValidFieldWithInvalidData",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}},
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("invalid_path"),
},
},
},
},
wantErr: true,
wantErrStr: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "updateImmutableCredentialTypeField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"credentialType"}},
Item: &pb.CredentialLibrary{
CredentialType: string(globals.SshPrivateKeyCredentialType),
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "Cannot modify credential type.",
},
{
name: "updateImmutableTypeField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"type"}},
Item: &pb.CredentialLibrary{
Type: string(globals.SshCertificateCredentialType),
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "Cannot modify resource type.",
},
{
name: "unsetVaultPath",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}},
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(""),
},
},
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "attributes.path\", desc: \"This is a required field and cannot be set to empty.",
},
{
name: "updateName",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
return cl
},
},
{
name: "updateDescription",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"description"}},
Item: &pb.CredentialLibrary{
Description: wrapperspb.String("updated_description"),
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Description = wrapperspb.String("updated_description")
return cl
},
},
{
name: "updateValidVaultPath",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}},
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/static-cred/updated/path"),
},
},
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.GetVaultLdapCredentialLibraryAttributes().Path = wrapperspb.String("ldap/static-cred/updated/path")
return cl
},
},
{
name: "updateMultipleOneNonExistent",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "doesntExist"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
return cl
},
},
{
name: "updateMultipleOneImmutable",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "credentialStoreId"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
CredentialStoreId: "immutable",
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
return cl
},
},
{
name: "updateMultiple",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "attributes.path"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/updated/path"),
},
},
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
cl.GetVaultLdapCredentialLibraryAttributes().Path = wrapperspb.String("ldap/creds/updated/path")
return cl
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
st, cleanup := freshLibrary()
defer cleanup()
if tc.req.Item.GetVersion() == 0 {
tc.req.Item.Version = 1
}
if tc.req.GetId() == "" {
tc.req.Id = st.GetPublicId()
}
resToChange, err := s.GetCredentialLibrary(ctx, &pbs.GetCredentialLibraryRequest{Id: st.GetPublicId()})
require.NoError(err)
got, gErr := s.UpdateCredentialLibrary(ctx, tc.req)
if tc.wantErr {
require.ErrorContains(gErr, tc.wantErrStr)
gApiErr, ok := gErr.(*handlers.ApiError)
if ok {
// We don't always return status.
require.NotNil(gApiErr)
require.EqualValues(tc.wantErrStatus, gApiErr.Status)
}
require.Nil(got)
return
}
require.NoError(gErr)
require.NotNil(got)
want := &pbs.UpdateCredentialLibraryResponse{Item: tc.res(resToChange.GetItem())}
gotUpdateTime := got.GetItem().GetUpdatedTime()
created := st.GetCreateTime().GetTimestamp()
assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
want.Item.UpdatedTime = got.Item.UpdatedTime
assert.EqualValues(2, got.Item.Version)
want.Item.Version = 2
assert.Empty(cmp.Diff(
got,
want,
protocmp.Transform(),
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
))
})
}
}
func TestListPagination(t *testing.T) {
// Set database read timeout to avoid duplicates in response
oldReadTimeout := globals.RefreshReadLookbackDuration
@@ -3292,6 +3731,9 @@ func TestListPagination(t *testing.T) {
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 5) {
allCredentialLibraries = append(allCredentialLibraries, sshCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 5) {
allCredentialLibraries = append(allCredentialLibraries, ldapCredentialLibraryToProto(l, prj))
}
// Reverse as we return items sorted by create_time desceding (newest first)
slices.Reverse(allCredentialLibraries)
@@ -3345,7 +3787,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
EstItemCount: 15,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3370,7 +3812,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
EstItemCount: 15,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3382,10 +3824,10 @@ func TestListPagination(t *testing.T) {
// Request rest of results
req.ListToken = got.ListToken
req.PageSize = 10
req.PageSize = 15
got, err = s.ListCredentialLibraries(ctx, req)
require.NoError(err)
require.Len(got.GetItems(), 6)
require.Len(got.GetItems(), 11)
// Compare without comparing the list token
assert.Empty(
cmp.Diff(
@@ -3396,7 +3838,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
EstItemCount: 15,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3421,14 +3863,14 @@ func TestListPagination(t *testing.T) {
// Update one of the other stores
allCredentialLibraries[1].Name = wrapperspb.String("new-name")
allCredentialLibraries[1].Version = 2
updatedLibrary := &vault.SSHCertificateCredentialLibrary{
SSHCertificateCredentialLibrary: &store.SSHCertificateCredentialLibrary{
updatedLibrary := &vault.LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
PublicId: allCredentialLibraries[1].GetId(),
StoreId: allCredentialLibraries[1].GetCredentialStoreId(),
Name: allCredentialLibraries[1].Name.Value,
},
}
cred, _, err := vaultRepo.UpdateSSHCertificateCredentialLibrary(ctx, prj.PublicId, updatedLibrary, 1, []string{"name"})
cred, _, err := vaultRepo.UpdateLdapCredentialLibrary(ctx, prj.PublicId, updatedLibrary, 1, []string{"name"})
require.NoError(err)
allCredentialLibraries[1].UpdatedTime = cred.UpdateTime.GetTimestamp()
allCredentialLibraries[1].Version = cred.Version
@@ -3462,7 +3904,7 @@ func TestListPagination(t *testing.T) {
SortDir: "desc",
// Should contain the deleted library
RemovedIds: []string{deletedCredLib.Id},
EstItemCount: 10,
EstItemCount: 15,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3487,7 +3929,7 @@ func TestListPagination(t *testing.T) {
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
EstItemCount: 15,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3515,7 +3957,7 @@ func TestListPagination(t *testing.T) {
SortDir: "desc",
// Should be empty again
RemovedIds: nil,
EstItemCount: 10,
EstItemCount: 15,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3538,7 +3980,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
EstItemCount: 15,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -2056,6 +2056,7 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
for _, cl := range req.GetBrokeredCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl),
globals.VaultCredentialLibraryPrefix,
globals.VaultLdapCredentialLibraryPrefix,
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
@@ -2069,6 +2070,7 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
if !handlers.ValidId(handlers.Id(cl),
globals.VaultCredentialLibraryPrefix,
globals.VaultSshCertificateCredentialLibraryPrefix,
globals.VaultLdapCredentialLibraryPrefix,
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
@@ -2094,6 +2096,7 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
for _, cl := range req.GetBrokeredCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl),
globals.VaultCredentialLibraryPrefix,
globals.VaultLdapCredentialLibraryPrefix,
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
@@ -2107,6 +2110,7 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
if !handlers.ValidId(handlers.Id(cl),
globals.VaultCredentialLibraryPrefix,
globals.VaultSshCertificateCredentialLibraryPrefix,
globals.VaultLdapCredentialLibraryPrefix,
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
@@ -2136,6 +2140,7 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
for _, cl := range req.GetBrokeredCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl),
globals.VaultCredentialLibraryPrefix,
globals.VaultLdapCredentialLibraryPrefix,
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
@@ -2149,6 +2154,7 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
if !handlers.ValidId(handlers.Id(cl),
globals.VaultCredentialLibraryPrefix,
globals.VaultSshCertificateCredentialLibraryPrefix,
globals.VaultLdapCredentialLibraryPrefix,
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
@@ -264,6 +264,7 @@ begin;
('GET'),
('POST');
-- Renamed in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_library (
public_id wt_public_id primary key,
store_id wt_public_id not null
@@ -379,6 +380,7 @@ begin;
('expired'),
('unknown');
-- Updated in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_credential (
public_id wt_public_id primary key,
library_id wt_public_id
@@ -0,0 +1,506 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
create table credential_vault_ldap_library (
public_id wt_public_id primary key,
project_id wt_public_id not null,
store_id wt_public_id not null
constraint credential_vault_store_fkey
references credential_vault_store (public_id)
on delete cascade
on update cascade,
name wt_name,
description wt_description,
create_time wt_timestamp,
update_time wt_timestamp,
version wt_version,
vault_path text not null
constraint vault_path_must_not_be_empty
check(length(trim(vault_path)) > 0)
constraint vault_path_must_have_staticcred_or_creds
check(vault_path ~ '^.+\/(static-cred|creds)\/(?:[^\/\\\s]+\/)*[^\/\\\s]+$'),
credential_type text not null
constraint credential_type_enm_fkey
references credential_type_enm (name)
on delete restrict
on update cascade,
constraint credential_vault_ldap_library_store_id_public_id_uq
unique(store_id, public_id),
constraint credential_vault_ldap_library_store_id_name_uq
unique(store_id, name),
constraint credential_vault_library_fkey
foreign key (project_id, store_id, public_id, credential_type)
references credential_vault_library (project_id, store_id, public_id, credential_type)
on delete cascade
on update cascade
);
comment on table credential_vault_ldap_library is
'credential_vault_ldap_library is a credential library that issues credentials from a vault ldap secret backend.';
create trigger insert_credential_vault_library_subtype before insert on credential_vault_ldap_library
for each row execute procedure insert_credential_vault_library_subtype();
create trigger update_credential_vault_library_table_update_time before update on credential_vault_ldap_library
for each row execute procedure update_credential_vault_library_table_update_time();
create trigger immutable_columns before update on credential_vault_ldap_library
for each row execute procedure immutable_columns('public_id', 'store_id', 'project_id', 'credential_type', 'create_time');
create trigger delete_credential_vault_library_subtype after delete on credential_vault_ldap_library
for each row execute procedure delete_credential_vault_library_subtype();
create trigger update_version_column after update on credential_vault_ldap_library
for each row execute procedure update_version_column();
create trigger update_time_column before update on credential_vault_ldap_library
for each row execute procedure update_time_column();
create trigger default_create_time_column before insert on credential_vault_ldap_library
for each row execute procedure default_create_time();
create trigger before_insert_credential_vault_library before insert on credential_vault_ldap_library
for each row execute procedure before_insert_credential_vault_library();
create function default_vault_ldap_credential_type() returns trigger
as $$
begin
if new.credential_type is distinct from 'username_password_domain' then
new.credential_type = 'username_password_domain';
end if;
return new;
end;
$$ language plpgsql;
comment on function default_vault_ldap_credential_type is
'default_vault_ldap_credential_type ensures the credential_type is set to username_password_domain';
create trigger default_vault_ldap_credential_type before insert on credential_vault_ldap_library
for each row execute procedure default_vault_ldap_credential_type();
-- Replaces view from 99/01_credential_vault_library_refactor.up.sql
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_username_password_mapping_ovrd
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
),
password_domain_override (library_id, username_attribute, password_attribute, domain_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override')),
nullif(domain_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
null as key_type,
null as key_bits,
null as username,
null as ttl,
null as key_id,
null as critical_options,
null as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
coalesce(upasso.username_attribute, sshpk.username_attribute, pd.username_attribute)
as username_attribute,
coalesce(upasso.password_attribute, pd.password_attribute)
as password_attribute,
pd.domain_attribute as domain_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute,
'generic' as cred_lib_type, -- used to switch on
null as additional_valid_principals
from credential_vault_generic_library library
join credential_vault_store_client store
on library.store_id = store.public_id
left join password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id
left join password_domain_override pd
on library.public_id = pd.library_id
union
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
null as http_method,
null as http_request_body,
library.credential_type as credential_type,
library.key_type as key_type,
library.key_bits as key_bits,
library.username as username,
library.ttl as ttl,
library.key_id as key_id,
library.critical_options as critical_options,
library.extensions as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
null as username_attribute,
null as password_attribute,
null as domain_attribute,
null as private_key_attribute,
null as private_key_passphrase_attribute,
'ssh-signed-cert' as cred_lib_type, -- used to switch on
additional_valid_principals as additional_valid_principals
from credential_vault_ssh_cert_library library
join credential_vault_store_client store
on library.store_id = store.public_id
union
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
null as http_method,
null as http_request_body,
library.credential_type as credential_type,
null as key_type,
null as key_bits,
null as username,
null as ttl,
null as key_id,
null as critical_options,
null as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
pd.username_attribute as username_attribute,
pd.password_attribute as password_attribute,
pd.domain_attribute as domain_attribute,
null as private_key_attribute,
null as private_key_passphrase_attribute,
'ldap' as cred_lib_type, -- used to switch on
null as additional_valid_principals
from credential_vault_ldap_library library
join credential_vault_store_client store
on library.store_id = store.public_id
left join password_domain_override pd
on library.public_id = pd.library_id;
comment on view credential_vault_library_issue_credentials is
'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
'This view should not be used to retrieve data which will be returned external to boundary.';
insert into oplog_ticket (name, version)
values
('credential_vault_ldap_library', 1);
create table credential_vault_ldap_library_hst (
public_id wt_public_id not null,
project_id wt_public_id not null,
store_id wt_public_id not null,
name wt_name,
description wt_description,
vault_path text not null,
credential_type text not null,
history_id wt_url_safe_id default wt_url_safe_id() primary key
constraint credential_library_history_base_fkey
references credential_library_history_base (history_id)
on delete cascade
on update cascade,
valid_range tstzrange not null default tstzrange(current_timestamp, null),
constraint credential_vault_ldap_library_hst_valid_range_excl
exclude using gist (public_id with =, valid_range with &&)
);
comment on table credential_vault_ldap_library_hst is
'credential_vault_ldap_library_hst is a history table where each row contains values from a row '
'in the credential_vault_ldap_library table during the time range in the valid_range column';
create trigger insert_credential_library_history_subtype before insert on credential_vault_ldap_library_hst
for each row execute function insert_credential_library_history_subtype();
create trigger delete_credential_library_history_subtype after delete on credential_vault_ldap_library_hst
for each row execute function delete_credential_library_history_subtype();
create trigger hst_on_insert after insert on credential_vault_ldap_library
for each row execute function hst_on_insert();
create trigger hst_on_update after update on credential_vault_ldap_library
for each row execute function hst_on_update();
create trigger hst_on_delete after delete on credential_vault_ldap_library
for each row execute function hst_on_delete();
create view credential_vault_ldap_library_hst_aggregate as
select
rdc.recording_id,
vll.public_id,
vll.name,
vll.description,
vll.vault_path,
vll.credential_type,
vsh.public_id as store_public_id,
vsh.project_id as store_project_id,
vsh.name as store_name,
vsh.description as store_description,
vsh.vault_address as store_vault_address,
vsh.namespace as store_namespace,
vsh.tls_server_name as store_tls_server_name,
vsh.tls_skip_verify as store_tls_skip_verify,
vsh.worker_filter as store_worker_filter,
string_agg(distinct rdc.credential_purpose, '|') as purposes
from credential_vault_ldap_library_hst as vll
left join recording_dynamic_credential as rdc on vll.history_id = rdc.credential_library_hst_id
join credential_vault_store_hst as vsh on rdc.credential_vault_store_hst_id = vsh.history_id
group by vll.history_id, rdc.recording_id, vsh.history_id;
comment on view credential_vault_ldap_library_hst_aggregate is
'credential_vault_ldap_library_hst_aggregate contains the vault ldap library history data along with its store and purpose data.';
create table credential_vault_ldap_library_deleted (
public_id wt_public_id primary key,
delete_time wt_timestamp not null
);
comment on table credential_vault_ldap_library_deleted is
'credential_vault_ldap_library_deleted holds the ID and delete_time '
'of every deleted vault ldap credential'
'It is automatically trimmed of records older than 30 days by a job.';
create index credential_vault_ldap_library_deleted_delete_time_idx on credential_vault_ldap_library_deleted (delete_time);
create trigger insert_deleted_id after delete on credential_vault_ldap_library
for each row execute function insert_deleted_id('credential_vault_ldap_library_deleted');
-- Replaces the function in 99/01_credential_vault_library_refactor.up.sql
drop trigger insert_recording_dynamic_credentials on recording_session;
drop function insert_recording_dynamic_credentials();
create function insert_recording_dynamic_credentials() returns trigger
as $$
begin
with
session_recording(session_id, recording_id) as (
select session_id, public_id
from recording_session
where session_id = new.session_id
),
session_dynamic_creds(library_id, purpose, recording_id) as (
select library_id, credential_purpose, recording_id
from session_credential_dynamic
join session_recording using (session_id)
),
library_history(public_id, store_id, library_hst_id, valid_range) as (
select public_id, store_id, history_id, valid_range
from credential_vault_generic_library_hst
union
select public_id, store_id, history_id, valid_range
from credential_vault_ssh_cert_library_hst
union
select public_id, store_id, history_id, valid_range
from credential_vault_ldap_library_hst
),
final(recording_id, library_id, store_id, library_hst_id, store_hst_id, cred_purpose) as (
select sdc.recording_id, lib.public_id, lib.store_id, lib.library_hst_id, store_hst.history_id, sdc.purpose
from library_history as lib
join credential_vault_store_hst as store_hst on lib.store_id = store_hst.public_id
and store_hst.valid_range @> current_timestamp
join session_dynamic_creds as sdc on lib.public_id = sdc.library_id
where lib.public_id in (select library_id from session_dynamic_creds)
and lib.valid_range @> current_timestamp
)
insert into recording_dynamic_credential
(recording_id, credential_vault_store_hst_id, credential_library_hst_id, credential_purpose)
select recording_id, store_hst_id, library_hst_id, cred_purpose
from final;
return new;
end;
$$ language plpgsql;
comment on function insert_recording_dynamic_credentials is
'insert_recording_dynamic_credentials is an after insert trigger for the recording_session table.';
create trigger insert_recording_dynamic_credentials after insert on recording_session
for each row execute procedure insert_recording_dynamic_credentials();
-- Replaces view defined in 99/01_credential_vault_library_refactor.up.sql
drop view whx_credential_dimension_source;
create view whx_credential_dimension_source as
with vault_generic_library as (
select vcl.public_id as public_id,
'vault generic credential library' as type,
coalesce(vcl.name, 'None') as name,
coalesce(vcl.description, 'None') as description,
vcl.vault_path as vault_path,
vcl.http_method as http_method,
case
when vcl.http_method = 'GET' then 'Not Applicable'
else coalesce(vcl.http_request_body::text, 'None')
end as http_request_body,
'Not Applicable' as username,
'Not Applicable' as key_type_and_bits
from credential_vault_generic_library as vcl
),
vault_ssh_cert_library as (
select vsccl.public_id as public_id,
'vault ssh certificate credential library' as type,
coalesce(vsccl.name, 'None') as name,
coalesce(vsccl.description, 'None') as description,
vsccl.vault_path as vault_path,
'Not Applicable' as http_method,
'Not Applicable' as http_request_body,
vsccl.username as username,
case
when vsccl.key_type = 'ed25519' then vsccl.key_type
else vsccl.key_type || '-' || vsccl.key_bits::text
end as key_type_and_bits
from credential_vault_ssh_cert_library as vsccl
),
vault_ldap_library as (
select vldapcl.public_id as public_id,
'vault ldap credential library' as type,
coalesce(vldapcl.name, 'None') as name,
coalesce(vldapcl.description, 'None') as description,
vldapcl.vault_path as vault_path,
'Not Applicable' as http_method,
'Not Applicable' as http_request_body,
'Not Applicable' as username,
'Not Applicable' as key_type_and_bits
from credential_vault_ldap_library as vldapcl
),
final as (
select s.public_id as session_id,
scd.credential_purpose as credential_purpose,
cl.public_id as credential_library_id,
coalesce(vcl.type, vsccl.type, vldapcl.type) as credential_library_type,
coalesce(vcl.name, vsccl.name, vldapcl.name) as credential_library_name,
coalesce(vcl.description, vsccl.description, vldapcl.description) as credential_library_description,
coalesce(vcl.vault_path, vsccl.vault_path, vldapcl.vault_path) as credential_library_vault_path,
coalesce(vcl.http_method, vsccl.http_method, vldapcl.http_method) as credential_library_vault_http_method,
coalesce(vcl.http_request_body, vsccl.http_request_body, vldapcl.http_request_body) as credential_library_vault_http_request_body,
coalesce(vcl.username, vsccl.username, vldapcl.username) as credential_library_username,
coalesce(vcl.key_type_and_bits, vsccl.key_type_and_bits, vldapcl.key_type_and_bits) as credential_library_key_type_and_bits,
cs.public_id as credential_store_id,
case
when vcs is null then 'None'
else 'vault credential store'
end as credential_store_type,
coalesce(vcs.name, 'None') as credential_store_name,
coalesce(vcs.description, 'None') as credential_store_description,
coalesce(vcs.namespace, 'None') as credential_store_vault_namespace,
coalesce(vcs.vault_address, 'None') as credential_store_vault_address,
t.public_id as target_id,
case
when tt.type = 'tcp' then 'tcp target'
when tt.type = 'ssh' then 'ssh target'
when tt.type = 'rdp' then 'rdp target'
else 'Unknown'
end as target_type,
coalesce(tt.name, 'None') as target_name,
coalesce(tt.description, 'None') as target_description,
coalesce(tt.default_port, 0) as target_default_port_number,
tt.session_max_seconds as target_session_max_seconds,
tt.session_connection_limit as target_session_connection_limit,
p.public_id as project_id,
coalesce(p.name, 'None') as project_name,
coalesce(p.description, 'None') as project_description,
o.public_id as organization_id,
coalesce(o.name, 'None') as organization_name,
coalesce(o.description, 'None') as organization_description
from session_credential_dynamic as scd
join session as s on scd.session_id = s.public_id
join credential_library as cl on scd.library_id = cl.public_id
join credential_store as cs on cl.store_id = cs.public_id
join target as t on s.target_id = t.public_id
join iam_scope as p on p.public_id = t.project_id and p.type = 'project'
join iam_scope as o on p.parent_id = o.public_id and o.type = 'org'
left join vault_generic_library as vcl on cl.public_id = vcl.public_id
left join vault_ssh_cert_library as vsccl on cl.public_id = vsccl.public_id
left join vault_ldap_library as vldapcl on cl.public_id = vldapcl.public_id
left join credential_vault_store as vcs on cs.public_id = vcs.public_id
left join target_all_subtypes as tt on t.public_id = tt.public_id
)
select session_id,
credential_purpose,
credential_library_id,
credential_library_type,
credential_library_name,
credential_library_description,
credential_library_vault_path,
credential_library_vault_http_method,
credential_library_vault_http_request_body,
credential_library_username,
credential_library_key_type_and_bits,
credential_store_id,
credential_store_type,
credential_store_name,
credential_store_description,
credential_store_vault_namespace,
credential_store_vault_address,
target_id,
target_type,
target_name,
target_description,
target_default_port_number,
target_session_max_seconds,
target_session_connection_limit,
project_id,
project_name,
project_description,
organization_id,
organization_name,
organization_description
from final;
commit;
@@ -3,6 +3,7 @@
begin;
-- Renamed in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_library_mapping_override (
library_id wt_public_id primary key
constraint credential_vault_library_fkey
@@ -16,6 +17,7 @@ begin;
-- insert_credential_vault_library_mapping_override_subtype() is a before insert trigger
-- function for subtypes of credential_vault_library_mapping_override
-- Updated and renamed in 99/01_credential_vault_library_refactor.up.sql
create function insert_credential_vault_library_mapping_override_subtype() returns trigger
as $$
begin
@@ -29,6 +31,7 @@ begin;
-- delete_credential_vault_library_mapping_override_subtype() is an after delete trigger
-- function for subtypes of credential_vault_library_mapping_override
-- Updated and renamed in 99/01_credential_vault_library_refactor.up.sql
create function delete_credential_vault_library_mapping_override_subtype() returns trigger
as $$
begin
@@ -3,7 +3,8 @@
begin;
-- Updated in 36/01_vault_library_map_username_password.up.sql
-- Updated in 36/01_vault_library_map_username_password.up.sql
-- Renamed in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_library_user_password_mapping_override (
library_id wt_public_id primary key
constraint credential_vault_library_fkey
@@ -3,7 +3,8 @@
begin;
-- Renames table from 22/04_vault_library_map_user_password.up.sql
-- Renames table from 22/04_vault_library_map_user_password.up.sql
-- Renamed again in 99/01_credential_vault_library_refactor.up.sql
alter table credential_vault_library_user_password_mapping_override
rename to credential_vault_library_username_password_mapping_override;
comment on table credential_vault_library_username_password_mapping_override is
@@ -7,6 +7,7 @@ begin;
insert into credential_type_enm (name)
values ('ssh_private_key');
-- Renamed in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_library_ssh_private_key_mapping_override (
library_id wt_public_id primary key
constraint credential_vault_library_fkey
@@ -93,6 +93,7 @@ begin;
('rsa', 3072),
('rsa', 4096);
-- Updated in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_ssh_cert_library (
public_id wt_public_id primary key,
store_id wt_public_id not null
@@ -4,6 +4,7 @@
begin;
-- Replaces view from 56/02_add_data_key_foreign_key_references.up.sql
-- Replaced in 78/01_ssh_signed_certs_additional_valid_principals
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
@@ -37,6 +37,7 @@ begin;
'delete_credential_library_history_subtype() is an after delete trigger '
'function for subtypes of credential_library_history_base.';
-- Renamed in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_library_hst (
public_id wt_public_id not null,
name wt_name,
@@ -33,6 +33,7 @@ begin;
'along with the values of those entities at the time of the recording. '
'These values are also stored in the BSR file.';
-- Updated in 99/01_credential_vault_library_refactor.up.sql
create function insert_recording_dynamic_credentials() returns trigger
as $$
begin
@@ -66,7 +66,8 @@ group by sspkc.history_id, rsc.recording_id, css.history_id;
comment on view credential_static_ssh_private_key_credential_hst_aggregate is
'credential_static_ssh_private_key_credential_hst_aggregate contains the ssh private key credential history data along with its store and purpose data.';
-- Dynamic Credentials
-- Replaced in 99/01_credential_vault_library_refactor.up.sql. Note that this
-- view's name has changed to credential_vault_generic_library_hst_aggregate.
create view credential_vault_library_hst_aggregate as
select
rdc.recording_id,
@@ -118,6 +118,7 @@ begin;
'It is automatically trimmed of records older than 30 days by a job.';
-- Credential libraries
-- Renamed in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_library_deleted (
public_id wt_public_id primary key,
delete_time wt_timestamp not null
@@ -3,6 +3,7 @@
begin;
-- Renamed in 99/01_credential_vault_library_refactor.up.sql
create table credential_vault_library_username_password_domain_mapping_ovrd (
library_id wt_public_id primary key
constraint credential_vault_library_fkey
@@ -38,6 +39,7 @@ begin;
-- Replaces view from 78/01_ssh_signed_certs_additional_valid_principals.up.sql
-- Replaced in 99/01_credential_vault_library_refactor.up.sql
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
@@ -161,6 +163,8 @@ begin;
-- Replaces view created in 49/01_vault_credentials.up.sql
-- Replaced in 99/01_credential_vault_library_refactor.up.sql where this
-- view's name changed to credential_vault_generic_library_list_lookup.
drop view credential_vault_library_list_lookup;
create view credential_vault_library_list_lookup as
with
@@ -355,6 +355,7 @@ begin;
-- The whx_credential_dimension_source view shows the current values in the
-- operational tables of the credential dimension.
-- Replaces whx_credential_dimension_source defined in 71/07_targets.up.sql
-- Replaced in 99/01_credential_vault_library_refactor.up.sql.
create view whx_credential_dimension_source as
with vault_generic_library as (
select vcl.public_id as public_id,
@@ -0,0 +1,765 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
-- For context, check the README.md in this folder.
begin;
-- Main table.
-- Renames the table defined in 10/04_vault_credential.up.sql
alter table credential_vault_library
rename to credential_vault_generic_library;
alter table credential_vault_generic_library
rename constraint credential_vault_library_pkey to credential_vault_generic_library_pkey;
alter table credential_vault_generic_library
rename constraint credential_vault_library_store_id_name_uq to credential_vault_generic_library_store_id_name_uq;
alter table credential_vault_generic_library
rename constraint credential_vault_library_store_id_public_id_uq to credential_vault_generic_library_store_id_public_id_uq;
comment on table credential_vault_generic_library is
'credential_vault_generic_library is a table where each row is a resource that represents a vault generic credential library. '
'It is a credential_vault_library subtype and a child table of credential_vault_store.';
drop trigger insert_deleted_id on credential_vault_generic_library;
create trigger insert_deleted_id after delete on credential_vault_generic_library
for each row execute procedure insert_deleted_id('credential_vault_generic_library_deleted');
insert into oplog_ticket
(name, version)
values
('credential_vault_generic_library', 1)
on conflict do nothing;
delete from oplog_ticket where name = 'credential_vault_library';
-- History table.
-- Renames the table defined in 71/15_dynamic_credential_history.up.sql
alter table credential_vault_library_hst
rename to credential_vault_generic_library_hst;
alter table credential_vault_generic_library_hst
rename constraint credential_vault_library_hst_pkey to credential_vault_generic_library_hst_pkey;
alter table credential_vault_generic_library_hst
rename constraint credential_vault_library_hst_valid_range_excl to credential_vault_generic_library_hst_valid_range_excl;
comment on table credential_vault_generic_library_hst is
'credential_vault_generic_library_hst is a history table where each row contains the values from '
'a row in the credential_vault_library table during the time range in the valid_range column.';
-- Updates the function defined in 71/16_recording_dynamic_credential.up.sql
-- Updated in 100/01_credential_vault_ldap_library.up.sql
drop trigger insert_recording_dynamic_credentials on recording_session;
drop function insert_recording_dynamic_credentials();
create function insert_recording_dynamic_credentials() returns trigger
as $$
begin
with
session_recording(session_id, recording_id) as (
select session_id, public_id
from recording_session
where session_id = new.session_id
),
session_dynamic_creds(library_id, purpose, recording_id) as (
select library_id, credential_purpose, recording_id
from session_credential_dynamic
join session_recording using (session_id)
),
library_history(public_id, store_id, library_hst_id, valid_range) as (
select public_id, store_id, history_id, valid_range
from credential_vault_generic_library_hst
union
select public_id, store_id, history_id, valid_range
from credential_vault_ssh_cert_library_hst
),
final(recording_id, library_id, store_id, library_hst_id, store_hst_id, cred_purpose) as (
select sdc.recording_id, lib.public_id, lib.store_id, lib.library_hst_id, store_hst.history_id, sdc.purpose
from library_history as lib
join credential_vault_store_hst as store_hst on lib.store_id = store_hst.public_id
and store_hst.valid_range @> current_timestamp
join session_dynamic_creds as sdc on lib.public_id = sdc.library_id
where lib.public_id in (select library_id from session_dynamic_creds)
and lib.valid_range @> current_timestamp
)
insert into recording_dynamic_credential
(recording_id, credential_vault_store_hst_id, credential_library_hst_id, credential_purpose)
select recording_id, store_hst_id, library_hst_id, cred_purpose
from final;
return new;
end;
$$ language plpgsql;
comment on function insert_recording_dynamic_credentials is
'insert_recording_dynamic_credentials is an after insert trigger for the recording_session table.';
create trigger insert_recording_dynamic_credentials after insert on recording_session
for each row execute procedure insert_recording_dynamic_credentials();
-- Deleted table.
-- Renames table defined in 81/01_deleted_tables_and_triggers.up.sql
alter table credential_vault_library_deleted
rename to credential_vault_generic_library_deleted;
alter table credential_vault_generic_library_deleted
rename constraint credential_vault_library_deleted_pkey to credential_vault_generic_library_deleted_pkey;
alter index credential_vault_library_deleted_delete_time_idx
rename to credential_vault_generic_library_deleted_delete_time_idx;
comment on table credential_vault_generic_library_deleted is
'credential_vault_generic_library_deleted holds the ID and delete_time of every deleted Vault credential library. '
'It is automatically trimmed of records older than 30 days by a job.';
-- Mapping override base table.
-- Note that table/constraint/trigger names get very long in the mapping
-- overrides section, and Postgres limits us to 64-1 chars by default, so we
-- may have to restrict our naming.
-- Renames table defined in 22/03_vault_library_map.up.sql
alter table credential_vault_library_mapping_override
rename to credential_vault_generic_library_mapping_override;
alter table credential_vault_generic_library_mapping_override
rename constraint credential_vault_library_mapping_override_pkey to credential_vault_generic_library_mapping_override_pkey;
alter table credential_vault_generic_library_mapping_override
rename constraint credential_vault_library_fkey to credential_vault_generic_library_fkey;
comment on table credential_vault_generic_library_mapping_override is
'credential_vault_generic_library_mapping_override is a base table for the vault generic library mapping override type. '
'Each row is owned by a single vault generic library and maps 1-to-1 to a row in one of the vault generic library mapping override subtype tables.';
-- These triggers are recreated on each of the tables' specific sections below.
-- Renames and updates function defined in 22/03_vault_library_map.up.sql
drop trigger insert_credential_vault_library_mapping_override_subtype on credential_vault_library_username_password_mapping_override;
drop trigger insert_credential_vault_library_mapping_override_subtype on credential_vault_library_username_password_domain_mapping_ovrd;
drop trigger insert_credential_vault_library_mapping_override_subtype on credential_vault_library_ssh_private_key_mapping_override;
drop function insert_credential_vault_library_mapping_override_subtype;
create function insert_credential_vault_generic_library_mapping_override_subtyp() returns trigger
as $$
begin
insert into credential_vault_generic_library_mapping_override
(library_id)
values
(new.library_id);
return new;
end;
$$ language plpgsql;
comment on function insert_credential_vault_generic_library_mapping_override_subtyp() is
'insert_credential_vault_generic_library_mapping_override_subtyp() is a '
'before insert trigger function that inserts rows from subtype tables into '
'credential_vault_generic_library_mapping_override. It must be used on all '
'Vault generic credential library mapping subtype tables.';
-- These triggers are recreated on each of the tables' specific sections below.
-- Renames and updates function defined in 22/03_vault_library_map.up.sql
drop trigger delete_credential_vault_library_mapping_override_subtype on credential_vault_library_username_password_mapping_override;
drop trigger delete_credential_vault_library_mapping_override_subtype on credential_vault_library_username_password_domain_mapping_ovrd;
drop trigger delete_credential_vault_library_mapping_override_subtype on credential_vault_library_ssh_private_key_mapping_override;
drop function delete_credential_vault_library_mapping_override_subtype;
create function delete_credential_vault_generic_library_mapping_override_subtyp() returns trigger
as $$
begin
delete from credential_vault_generic_library_mapping_override
where library_id = old.library_id;
return null; -- result is ignored since this is an after trigger
end;
$$ language plpgsql;
comment on function delete_credential_vault_generic_library_mapping_override_subtyp() is
'delete_credential_vault_generic_library_mapping_override_subtyp() is a '
'before insert trigger function that deletes rows from '
'credential_vault_generic_library_mapping_override when they''re deleted '
'on the subtype tables. It must be used on all Vault generic credential '
'library mapping subtype tables.';
-- Mapping override: SSH PK table.
-- Renames table defined in 39/02_vault_ssh_private_key_override.up.sql
alter table credential_vault_library_ssh_private_key_mapping_override
rename to credential_vault_generic_library_ssh_private_key_mapping_ovrd;
alter table credential_vault_generic_library_ssh_private_key_mapping_ovrd
rename constraint credential_vault_library_ssh_private_key_mapping_override_pkey to credential_vault_generic_library_ssh_priv_key_mapping_ovrd_pkey;
alter table credential_vault_generic_library_ssh_private_key_mapping_ovrd
rename constraint credential_vault_library_mapping_override_fkey to credential_vault_generic_library_mapping_override_fkey;
alter table credential_vault_generic_library_ssh_private_key_mapping_ovrd
rename constraint credential_vault_library_fkey to credential_vault_generic_library_fkey;
comment on table credential_vault_generic_library_ssh_private_key_mapping_ovrd is
'credential_vault_generic_library_ssh_private_key_mapping_ovrd is a table '
'where each row represents a mapping that overrides the default mapping '
'from a generic vault secret to a ssh private key credential type '
'for a vault generic credential library.';
create trigger insert_credential_vault_generic_library_mapping_override_subtyp before insert on credential_vault_generic_library_ssh_private_key_mapping_ovrd
for each row execute procedure insert_credential_vault_generic_library_mapping_override_subtyp();
create trigger delete_credential_vault_generic_library_mapping_override_subtyp after delete on credential_vault_generic_library_ssh_private_key_mapping_ovrd
for each row execute procedure delete_credential_vault_generic_library_mapping_override_subtyp();
-- Mapping override: Username & Password table.
-- Renames table in 36/01_vault_library_map_username_password.up.sql
alter table credential_vault_library_username_password_mapping_override
rename to credential_vault_generic_library_username_password_mapping_ovrd;
alter table credential_vault_generic_library_username_password_mapping_ovrd
rename constraint credential_vault_library_user_password_mapping_override_pkey to credential_vault_generic_library_usern_pass_mapping_ovrd_pkey;
alter table credential_vault_generic_library_username_password_mapping_ovrd
rename constraint credential_vault_library_fkey to credential_vault_generic_library_fkey;
alter table credential_vault_generic_library_username_password_mapping_ovrd
rename constraint credential_vault_library_mapping_override_fkey to credential_vault_generic_library_mapping_override_fkey;
comment on table credential_vault_generic_library_username_password_mapping_ovrd is
'credential_vault_generic_library_username_password_mapping_ovrd is a table '
'where each row represents a mapping that overrides the default mapping '
'from a generic vault secret to a username password credential type '
'for a vault generic credential library.';
create trigger insert_credential_vault_generic_library_mapping_override_subtyp before insert on credential_vault_generic_library_username_password_mapping_ovrd
for each row execute procedure insert_credential_vault_generic_library_mapping_override_subtyp();
create trigger delete_credential_vault_generic_library_mapping_override_subtyp after delete on credential_vault_generic_library_username_password_mapping_ovrd
for each row execute procedure delete_credential_vault_generic_library_mapping_override_subtyp();
-- Mapping override: Username, Password & Domain table.
-- Renames table defined in 98/02_username_password_domain_vault.up.sql
alter table credential_vault_library_username_password_domain_mapping_ovrd
rename to credential_vault_generic_library_usern_pass_domain_mapping_ovrd;
alter table credential_vault_generic_library_usern_pass_domain_mapping_ovrd
rename constraint credential_vault_library_username_password_domain_mapping__pkey to credential_vault_generic_library_upd_mapping_ovrd_pkey;
alter table credential_vault_generic_library_usern_pass_domain_mapping_ovrd
rename constraint credential_vault_library_fkey to credential_vault_generic_library_fkey;
alter table credential_vault_generic_library_usern_pass_domain_mapping_ovrd
rename constraint credential_vault_library_mapping_override_fkey to credential_vault_generic_library_mapping_override_fkey;
comment on table credential_vault_generic_library_usern_pass_domain_mapping_ovrd is
'credential_vault_generic_library_usern_pass_domain_mapping_ovrd is a table '
'where each row represents a mapping that overrides the default mapping '
'from a generic vault secret to a user password domain credential type '
'for a vault generic credential library.';
create trigger insert_credential_vault_generic_library_mapping_override_subtyp before insert on credential_vault_generic_library_usern_pass_domain_mapping_ovrd
for each row execute procedure insert_credential_vault_generic_library_mapping_override_subtyp();
create trigger delete_credential_vault_generic_library_mapping_override_subtyp after delete on credential_vault_generic_library_usern_pass_domain_mapping_ovrd
for each row execute procedure delete_credential_vault_generic_library_mapping_override_subtyp();
-- Views. Postgres already automatically updated them to reference the renamed
-- tables, however these should still be explictly in the migration.
-- > whx_credential_dimension_source
-- > credential_vault_library_issue_credentials
-- > credential_vault_library_list_lookup: Renamed to credential_vault_generic_library_list_lookup.
-- > credential_vault_library_hst_aggregate: Renamed to credential_vault_generic_library_hst_aggregate.
-- Replaces view defined in 98/04_rdp_targets.up.sql
-- Replaced in 100/01_credential_vault_ldap_library.up.sql
drop view whx_credential_dimension_source;
create view whx_credential_dimension_source as
with vault_generic_library as (
select vcl.public_id as public_id,
'vault generic credential library' as type,
coalesce(vcl.name, 'None') as name,
coalesce(vcl.description, 'None') as description,
vcl.vault_path as vault_path,
vcl.http_method as http_method,
case
when vcl.http_method = 'GET' then 'Not Applicable'
else coalesce(vcl.http_request_body::text, 'None')
end as http_request_body,
'Not Applicable' as username,
'Not Applicable' as key_type_and_bits
from credential_vault_generic_library as vcl
),
vault_ssh_cert_library as (
select vsccl.public_id as public_id,
'vault ssh certificate credential library' as type,
coalesce(vsccl.name, 'None') as name,
coalesce(vsccl.description, 'None') as description,
vsccl.vault_path as vault_path,
'Not Applicable' as http_method,
'Not Applicable' as http_request_body,
vsccl.username as username,
case
when vsccl.key_type = 'ed25519' then vsccl.key_type
else vsccl.key_type || '-' || vsccl.key_bits::text
end as key_type_and_bits
from credential_vault_ssh_cert_library as vsccl
),
final as (
select s.public_id as session_id,
scd.credential_purpose as credential_purpose,
cl.public_id as credential_library_id,
coalesce(vcl.type, vsccl.type) as credential_library_type,
coalesce(vcl.name, vsccl.name) as credential_library_name,
coalesce(vcl.description, vsccl.description) as credential_library_description,
coalesce(vcl.vault_path, vsccl.vault_path) as credential_library_vault_path,
coalesce(vcl.http_method, vsccl.http_method) as credential_library_vault_http_method,
coalesce(vcl.http_request_body, vsccl.http_request_body) as credential_library_vault_http_request_body,
coalesce(vcl.username, vsccl.username) as credential_library_username,
coalesce(vcl.key_type_and_bits, vsccl.key_type_and_bits) as credential_library_key_type_and_bits,
cs.public_id as credential_store_id,
case
when vcs is null then 'None'
else 'vault credential store'
end as credential_store_type,
coalesce(vcs.name, 'None') as credential_store_name,
coalesce(vcs.description, 'None') as credential_store_description,
coalesce(vcs.namespace, 'None') as credential_store_vault_namespace,
coalesce(vcs.vault_address, 'None') as credential_store_vault_address,
t.public_id as target_id,
case
when tt.type = 'tcp' then 'tcp target'
when tt.type = 'ssh' then 'ssh target'
when tt.type = 'rdp' then 'rdp target'
else 'Unknown'
end as target_type,
coalesce(tt.name, 'None') as target_name,
coalesce(tt.description, 'None') as target_description,
coalesce(tt.default_port, 0) as target_default_port_number,
tt.session_max_seconds as target_session_max_seconds,
tt.session_connection_limit as target_session_connection_limit,
p.public_id as project_id,
coalesce(p.name, 'None') as project_name,
coalesce(p.description, 'None') as project_description,
o.public_id as organization_id,
coalesce(o.name, 'None') as organization_name,
coalesce(o.description, 'None') as organization_description
from session_credential_dynamic as scd
join session as s on scd.session_id = s.public_id
join credential_library as cl on scd.library_id = cl.public_id
join credential_store as cs on cl.store_id = cs.public_id
join target as t on s.target_id = t.public_id
join iam_scope as p on p.public_id = t.project_id and p.type = 'project'
join iam_scope as o on p.parent_id = o.public_id and o.type = 'org'
left join vault_generic_library as vcl on cl.public_id = vcl.public_id
left join vault_ssh_cert_library as vsccl on cl.public_id = vsccl.public_id
left join credential_vault_store as vcs on cs.public_id = vcs.public_id
left join target_all_subtypes as tt on t.public_id = tt.public_id
)
select session_id,
credential_purpose,
credential_library_id,
credential_library_type,
credential_library_name,
credential_library_description,
credential_library_vault_path,
credential_library_vault_http_method,
credential_library_vault_http_request_body,
credential_library_username,
credential_library_key_type_and_bits,
credential_store_id,
credential_store_type,
credential_store_name,
credential_store_description,
credential_store_vault_namespace,
credential_store_vault_address,
target_id,
target_type,
target_name,
target_description,
target_default_port_number,
target_session_max_seconds,
target_session_connection_limit,
project_id,
project_name,
project_description,
organization_id,
organization_name,
organization_description
from final;
-- Replaces view defined in 98/02_username_password_domain_vault.up.sql
-- Replaced in 100/01_credential_vault_ldap_library.up.sql
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_username_password_mapping_ovrd
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
),
password_domain_override (library_id, username_attribute, password_attribute, domain_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override')),
nullif(domain_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
null as key_type,
null as key_bits,
null as username,
null as ttl,
null as key_id,
null as critical_options,
null as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
coalesce(upasso.username_attribute, sshpk.username_attribute, pd.username_attribute)
as username_attribute,
coalesce(upasso.password_attribute, pd.password_attribute)
as password_attribute,
pd.domain_attribute as domain_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute,
'generic' as cred_lib_type, -- used to switch on
null as additional_valid_principals
from credential_vault_generic_library library
join credential_vault_store_client store
on library.store_id = store.public_id
left join password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id
left join password_domain_override pd
on library.public_id = pd.library_id
union
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
null as http_method,
null as http_request_body,
library.credential_type as credential_type,
library.key_type as key_type,
library.key_bits as key_bits,
library.username as username,
library.ttl as ttl,
library.key_id as key_id,
library.critical_options as critical_options,
library.extensions as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
null as username_attribute,
null as password_attribute,
null as domain_attribute,
null as private_key_attribute,
null as private_key_passphrase_attribute,
'ssh-signed-cert' as cred_lib_type, -- used to switch on
additional_valid_principals as additional_valid_principals
from credential_vault_ssh_cert_library library
join credential_vault_store_client store
on library.store_id = store.public_id;
comment on view credential_vault_library_issue_credentials is
'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
'This view should not be used to retrieve data which will be returned external to boundary.';
-- Replaces and renames view defined in 98/02_username_password_domain_vault.up.sql.
alter view credential_vault_library_list_lookup
rename to credential_vault_generic_library_list_lookup;
drop view credential_vault_generic_library_list_lookup;
create view credential_vault_generic_library_list_lookup as
with
password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_username_password_mapping_ovrd
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
),
password_domain_override (library_id, username_attribute, password_attribute, domain_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override')),
nullif(domain_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
coalesce(upasso.username_attribute, sshpk.username_attribute, pd.username_attribute)
as username_attribute,
coalesce(upasso.password_attribute, pd.password_attribute)
as password_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute,
pd.domain_attribute as domain_attribute
from credential_vault_generic_library library
left join password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id
left join password_domain_override pd
on library.public_id = pd.library_id;
comment on view credential_vault_generic_library_list_lookup is
'credential_vault_generic_library_list_lookup is a view where each row contains a Vault generic credential library and any of library''s credential mapping overrides. '
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
-- Replaces and renames view defined in 71/17_credential_history_views.up.sql.
alter view credential_vault_library_hst_aggregate
rename to credential_vault_generic_library_hst_aggregate;
drop view credential_vault_generic_library_hst_aggregate;
create view credential_vault_generic_library_hst_aggregate as
select
rdc.recording_id,
vl.public_id,
vl.name,
vl.description,
vl.vault_path,
vl.http_method,
vl.http_request_body,
vl.credential_type,
vsh.public_id as store_public_id,
vsh.project_id as store_project_id,
vsh.name as store_name,
vsh.description as store_description,
vsh.vault_address as store_vault_address,
vsh.namespace as store_namespace,
vsh.tls_server_name as store_tls_server_name,
vsh.tls_skip_verify as store_tls_skip_verify,
vsh.worker_filter as store_worker_filter,
string_agg(distinct rdc.credential_purpose, '|') as purposes
from credential_vault_generic_library_hst as vl
left join recording_dynamic_credential as rdc on vl.history_id = rdc.credential_library_hst_id
join credential_vault_store_hst as vsh on rdc.credential_vault_store_hst_id = vsh.history_id
group by vl.history_id, rdc.recording_id, vsh.history_id;
comment on view credential_vault_generic_library_hst_aggregate is
'credential_vault_generic_library_hst_aggregate contains Vault generic credential library history data along with its store and purpose data.';
-- Renaming is complete. We can now redefine credential_vault_library and
-- rewire all the Vault subtype tables into this redefined table.
-- credential_vault_store doesn't have a unique constraint on (project_id,
-- public_id) fields. We'll need this to attach a FK to them later. There
-- should be no side-effects from this given that:
-- > public_id is a PK (already globally unique) and,
-- > credential_vault_store is a subtype table of credential_store, which
-- does have a (project_id, public_id) uniqueness constraint.
alter table credential_vault_store
add constraint credential_vault_store_project_id_public_id_uq
unique(project_id, public_id);
-- This statement defines the basic fields and pulls all the data from
-- credential_library. We can do this as a blanket select * statement because
-- only Vault-subtype credential libraries exist at this point.
--
-- We want to insert the data before any constraints are set because
-- credential_library has had a few revisions and while we have also updated
-- the data alongside those table revisions, success on an insert with
-- historical data isn't guaranteed. Additionally, we also want to do this
-- before any triggers are put in place.
create table credential_vault_library as
select * from credential_library;
alter table credential_vault_library
add primary key (public_id),
alter column store_id set not null,
alter column credential_type set not null,
alter column credential_type set default 'unspecified',
add constraint credential_type_enm_fkey
foreign key (credential_type)
references credential_type_enm (name)
on delete restrict
on update cascade,
alter column project_id set not null,
alter column create_time set not null,
alter column update_time set not null,
add constraint credential_library_fkey
foreign key (public_id, store_id, credential_type, project_id)
references credential_library (public_id, store_id, credential_type, project_id)
on delete cascade
on update cascade,
add constraint credential_vault_store_fkey
foreign key (project_id, store_id)
references credential_vault_store (project_id, public_id)
on delete cascade
on update cascade,
add constraint credential_vault_library_project_id_public_id_uq
unique(project_id, public_id),
add constraint credential_vault_library_project_store_public_ids_credtype_uq
unique(project_id, store_id, public_id, credential_type);
comment on table credential_vault_library is
'credential_vault_library is a base table for Vault credential library '
'subtypes and a child table of credential_vault_store. Each row maps 1-to-1 '
'to a row in one of the Vault credential library subtype tables.';
create trigger immutable_columns before update on credential_vault_library
for each row execute function immutable_columns('public_id', 'store_id', 'project_id', 'credential_type');
create trigger insert_credential_library_subtype before insert on credential_vault_library
for each row execute procedure insert_credential_library_subtype();
create trigger delete_credential_library_subtype after delete on credential_vault_library
for each row execute procedure delete_credential_library_subtype();
create trigger update_credential_library_table_update_time before update on credential_vault_library
for each row execute procedure update_credential_library_table_update_time();
create index credential_vault_library_create_time_public_id_idx
on credential_vault_library (create_time desc, public_id desc);
create index credential_vault_library_update_time_public_id_idx
on credential_vault_library (update_time desc, public_id desc);
create or replace function insert_credential_vault_library_subtype() returns trigger
as $$
begin
select project_id into new.project_id
from credential_store
where credential_store.public_id = new.store_id;
insert into credential_vault_library
(public_id, store_id, project_id, credential_type, create_time)
values
(new.public_id, new.store_id, new.project_id, new.credential_type, new.create_time);
return new;
end;
$$ language plpgsql;
comment on function insert_credential_vault_library_subtype() is
'insert_credential_vault_library_subtype is a before insert trigger '
'function that inserts rows from subtype tables into '
'credential_vault_library. It must be used on all Vault credential library '
'subtype tables.';
create function delete_credential_vault_library_subtype() returns trigger
as $$
begin
delete from credential_vault_library
where public_id = old.public_id;
return null; -- result is ignored since this is an after trigger
end;
$$ language plpgsql;
comment on function delete_credential_vault_library_subtype() is
'delete_credential_vault_library_subtype is an after delete trigger '
'function that deletes rows from credential_vault_library when they''re '
'deleted on the subtype tables. It must be used on all Vault credential '
'library subtype tables.';
create function update_credential_vault_library_table_update_time() returns trigger
as $$
begin
update credential_vault_library set update_time = now() where public_id = new.public_id;
return new;
end;
$$ language plpgsql;
comment on function update_credential_vault_library_table_update_time() is
'update_credential_vault_library_table_update_time is a before update '
'trigger function that updates the update_time column on '
'credential_vault_library. It must be used on all Vault credential library '
'subtype tables.';
-- We still have some entities on the Vault subtype tables that reference
-- credential_library when they should now reference credential_vault_library.
alter table credential_vault_generic_library
drop constraint credential_library_fkey,
add constraint credential_vault_library_fkey
foreign key (project_id, store_id, public_id, credential_type)
references credential_vault_library (project_id, store_id, public_id, credential_type)
on delete cascade
on update cascade;
-- Updates table defined in 63/01_credential_vault_ssh_cert_library.up.sql
alter table credential_vault_ssh_cert_library
drop constraint credential_library_fkey,
add constraint credential_vault_library_fkey
foreign key (project_id, store_id, public_id, credential_type)
references credential_vault_library (project_id, store_id, public_id, credential_type)
on delete cascade
on update cascade;
drop trigger insert_credential_library_subtype on credential_vault_generic_library;
create trigger insert_credential_vault_library_subtype before insert on credential_vault_generic_library
for each row execute procedure insert_credential_vault_library_subtype();
drop trigger delete_credential_library_subtype on credential_vault_generic_library;
create trigger delete_credential_vault_library_subtype after delete on credential_vault_generic_library
for each row execute procedure delete_credential_vault_library_subtype();
drop trigger update_credential_library_table_update_time on credential_vault_generic_library;
create trigger update_credential_vault_library_table_update_time before update on credential_vault_generic_library
for each row execute procedure update_credential_vault_library_table_update_time();
drop trigger insert_credential_library_subtype on credential_vault_ssh_cert_library;
create trigger insert_credential_vault_library_subtype before insert on credential_vault_ssh_cert_library
for each row execute procedure insert_credential_vault_library_subtype();
drop trigger delete_credential_library_subtype on credential_vault_ssh_cert_library;
create trigger delete_credential_vault_library_subtype after delete on credential_vault_ssh_cert_library
for each row execute procedure delete_credential_vault_library_subtype();
drop trigger update_credential_library_table_update_time on credential_vault_ssh_cert_library;
create trigger update_credential_vault_library_table_update_time before update on credential_vault_ssh_cert_library
for each row execute procedure update_credential_vault_library_table_update_time();
-- Finally, we can correct the restriction in credential_vault_credential.
-- credential_vault_library is now a superset of
-- credential_vault_generic_library, so this shouldn't have side-effects.
-- Updates the table defined in 10/04_vault_credential.up.sql
alter table credential_vault_credential
drop constraint credential_vault_library_fkey,
add constraint credential_vault_library_fkey
foreign key (library_id)
references credential_vault_library (public_id)
on delete set null
on update cascade;
commit;
@@ -0,0 +1,50 @@
# Vault Generic Credential Library DB Refactor
Historically, the "Vault generic" credential library was named just "Vault"
credential library. Over the course of Boundary's development, it was renamed to
"Vault generic" to better match its abilities and to conceptually separate it
from other Vault-specific credential libraries, however this nomenclature change
was not applied to database (or domain) entities. This migration applies this
nomenclature change at the database level, renaming all related entities to
match the new "Vault generic" name.
Currently, these database entities are either named or prefixed with
`credential_vault_library`. This migration changes them to be named or prefixed
with `credential_vault_generic_library`.
Additionally, `credential_vault_library` takes on a new meaning and is defined
as a new base table that holds information about all *Vault-specific* credential
libraries. `credential_vault_library` is a subtype table of
`credential_library`:
```
credential_library |> credential_vault_library |> credential_vault_generic_library
| |> credential_vault_ssh_cred_library
| |> any future Vault-specific credential libraries
|
In the future, we could also have:
|> base table for a non-Vault system's credlibs |> credential library subtype tables
```
While the history table for Vault generic credential libraries is renamed to
`credential_vault_generic_library_hst`, the overall history design remains
unchanged.
This pattern is being established due to the need to differentiate between the
systems that Boundary's credential libraries integrate with, for database
integrity purposes:
`credential_dynamic` is a base table that represents all renewable/revokable
dynamic credentials. `credential_vault_credential` is a subtype table that
represents renewable/revokable dynamic credentials (Vault leases). Given the
specificity of this design, these leases must be obtained by a Vault credential
library.
To enforce this, the `library_id` field in `credential_vault_credential` has a
foreign key constraint on `credential_vault_library(public_id)`, meaning only
Vault leases obtained by a Vault generic credential library can ever be inserted
into this table and therefore managed by Boundary.
With the future introduction of new Vault credential libraries that can also
issue Vault leases, this constraint becomes too restrictive and the design needs
to be extended. By redefining `credential_vault_library` as described above,
this refactor provides the backing for this to be done.
File diff suppressed because it is too large. Load diff
@@ -479,7 +479,7 @@ begin;
('p____rcolors', 'cvs__rcolors', 'red vault store', 'Some', 'https://red.vault.color', 'red'),
('p____gcolors', 'cvs__gcolors', 'green vault store', 'Maybe', 'https://green.vault.color', 'green');
insert into credential_vault_library
insert into credential_vault_generic_library
(store_id, public_id, name, description, vault_path, http_method)
values
('cvs__bcolors', 'cvl_______b1', 'blue vault library', 'None', '/secrets', 'GET'),
@@ -493,6 +493,13 @@ begin;
('cvs__rcolors', 'cvl__ssh__r1', 'red vault ssh library', '/ssh/issue/red', 'webdev', 'ecdsa', 521),
('cvs__gcolors', 'cvl__ssh__g1', 'green vault ssh library', '/ssh/issue/green', 'dba', 'rsa', 4096);
insert into credential_vault_ldap_library
(store_id, public_id, name, vault_path, credential_type)
values
('cvs__bcolors', 'cvl__ldap_b1', 'blue vault ldap library', '/ldap/static-cred/blue', 'username_password_domain'),
('cvs__rcolors', 'cvl__ldap_r1', 'red vault ldap library', '/ldap/creds/red', 'username_password_domain'),
('cvs__gcolors', 'cvl__ldap_g1', 'green vault ldap library', '/ldap/static-cred/org-colors-r-us/group-green-color/green','username_password_domain');
insert into credential_static_store
(project_id, public_id, name, description)
values
@@ -388,7 +388,7 @@ begin;
values
('vs_______wvs', 'kdkv___widget', 'current', 'hmac-value', 'token-value', now(), now() + interval '1 hour');
insert into credential_vault_library
insert into credential_vault_generic_library
(store_id, public_id, name, description, vault_path, http_method, credential_type)
values
('vs_______wvs', 'vl______wvl1', 'widget vault library', 'None', '/secrets', 'GET', 'unspecified'),
@@ -413,82 +413,82 @@ begin;
('vs_______wvs', 'vl______wvl20', 'widget vault kv eighteen', 'None', '/secrets/kv/eighteen', 'GET', 'username_password_domain'),
('vs_______wvs', 'vl______wvl21', 'widget vault kv nineteen', 'None', '/secrets/kv/nineteen', 'GET', 'username_password_domain');
insert into credential_vault_library_username_password_mapping_override
insert into credential_vault_generic_library_username_password_mapping_ovrd
(library_id)
values
('vl______wvl4');
insert into credential_vault_library_username_password_mapping_override
insert into credential_vault_generic_library_username_password_mapping_ovrd
(library_id, username_attribute)
values
('vl______wvl5', 'my_username');
insert into credential_vault_library_username_password_mapping_override
insert into credential_vault_generic_library_username_password_mapping_ovrd
(library_id, password_attribute)
values
('vl______wvl6', 'my_password');
insert into credential_vault_library_username_password_mapping_override
insert into credential_vault_generic_library_username_password_mapping_ovrd
(library_id, username_attribute, password_attribute)
values
('vl______wvl7', 'my_username', 'my_password');
insert into credential_vault_library_ssh_private_key_mapping_override
insert into credential_vault_generic_library_ssh_private_key_mapping_ovrd
(library_id)
values
('vl______wvl9');
insert into credential_vault_library_ssh_private_key_mapping_override
insert into credential_vault_generic_library_ssh_private_key_mapping_ovrd
(library_id, username_attribute)
values
('vl______wvl10', 'my_username');
insert into credential_vault_library_ssh_private_key_mapping_override
insert into credential_vault_generic_library_ssh_private_key_mapping_ovrd
(library_id, private_key_attribute)
values
('vl______wvl11', 'my_private_key');
insert into credential_vault_library_ssh_private_key_mapping_override
insert into credential_vault_generic_library_ssh_private_key_mapping_ovrd
(library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute)
values
('vl______wvl12', 'my_username', 'my_private_key', 'my_passphrase');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id)
values
('vl______wvl13');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id, username_attribute, password_attribute, domain_attribute)
values
('vl______wvl14', 'my_username', 'my_password', 'my_domain');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id, username_attribute, domain_attribute)
values
('vl______wvl15', 'my_username', 'my_domain');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id, password_attribute, domain_attribute)
values
('vl______wvl16', 'my_password', 'my_domain');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id, username_attribute, password_attribute)
values
('vl______wvl17', 'my_username', 'my_password');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id, username_attribute)
values
('vl______wvl18', 'my_username');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id, password_attribute)
values
('vl______wvl19', 'my_password');
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id, domain_attribute)
values
('vl______wvl20', 'my_domain');
@@ -0,0 +1,64 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
select plan(4);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
select lives_ok(
$$
insert into credential_vault_ldap_library
(store_id, public_id, vault_path, credential_type)
values
('vs_______wvs', 'vl______vlsc1', '/ldap/static-cred/foo', 'username_password_domain'),
('vs_______wvs', 'vl______vlsc2', '/ldap/static-cred/foo/bar', 'username_password_domain'),
('vs_______wvs', 'vl______vlsc3', '/ldap/static-cred/foo/bar/baz', 'username_password_domain'),
('vs_______wvs', 'vl______vlsc4', '/ldap/static-cred/foo/bar/baz/more/paths', 'username_password_domain'),
('vs_______wvs', 'vl______vldyn5', '/ldap/creds/foo', 'username_password_domain'),
('vs_______wvs', 'vl______vldyn6', '/ldap/creds/foo/bar/baz/more/paths', 'username_password_domain'),
('vs_______wvs', 'vl______vldyn7', '/ldap/creds/foo/bar/baz', 'something_that_doesnt_exist');
$$
);
select results_eq(
$$
select public_id, project_id, store_id, name, description, vault_path, credential_type
from credential_vault_ldap_library
where public_id like 'vl______vlsc%'
or public_id like 'vl______vldyn%'
order by public_id;
$$,
$$
values
('vl______vldyn5'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/creds/foo', 'username_password_domain'),
('vl______vldyn6'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/creds/foo/bar/baz/more/paths', 'username_password_domain'),
('vl______vldyn7'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/creds/foo/bar/baz', 'username_password_domain'),
('vl______vlsc1'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo', 'username_password_domain'),
('vl______vlsc2'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo/bar', 'username_password_domain'),
('vl______vlsc3'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo/bar/baz', 'username_password_domain'),
('vl______vlsc4'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo/bar/baz/more/paths', 'username_password_domain');
$$
);
select row_eq(
$$
select count(*)
from credential_vault_library
where public_id like 'vl______vlsc%'
or public_id like 'vl______vldyn%';
$$,
row(7::bigint)
);
select row_eq(
$$
select count(*)
from credential_library
where public_id like 'vl______vlsc%'
or public_id like 'vl______vldyn%';
$$,
row(7::bigint)
);
select * from finish();
rollback;
@@ -11,31 +11,31 @@ begin;
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate the setup data
select is(count(*), 1::bigint) from credential_vault_library where public_id = 'vl______wvl1' and credential_type = 'unspecified';
select is(count(*), 1::bigint) from credential_vault_generic_library where public_id = 'vl______wvl1' and credential_type = 'unspecified';
select is(count(*), 1::bigint) from credential_library where public_id = 'vl______wvl1' and credential_type = 'unspecified';
select is(count(*), 1::bigint) from credential_vault_library where public_id = 'vl______wvl3' and credential_type = 'username_password';
select is(count(*), 1::bigint) from credential_vault_generic_library where public_id = 'vl______wvl3' and credential_type = 'username_password';
select is(count(*), 1::bigint) from credential_library where public_id = 'vl______wvl3' and credential_type = 'username_password';
-- validate the insert triggers
prepare insert_vault_library as
insert into credential_vault_library
insert into credential_vault_generic_library
(store_id, public_id, vault_path, http_method, credential_type)
values
('vs_______wvs', 'vl_______tt1', '/secrets/kv', 'GET', 'username_password');
select lives_ok('insert_vault_library');
select is(count(*), 1::bigint) from credential_vault_library where public_id = 'vl_______tt1' and credential_type = 'username_password';
select is(count(*), 1::bigint) from credential_vault_generic_library where public_id = 'vl_______tt1' and credential_type = 'username_password';
select is(count(*), 1::bigint) from credential_library where public_id = 'vl_______tt1' and credential_type = 'username_password';
-- validate the delete triggers
prepare delete_vault_library as
delete
from credential_vault_library
from credential_vault_generic_library
where public_id = 'vl_______tt1';
select lives_ok('delete_vault_library');
select is(count(*), 0::bigint) from credential_vault_library where public_id = 'vl_______tt1';
select is(count(*), 0::bigint) from credential_vault_generic_library where public_id = 'vl_______tt1';
select is(count(*), 0::bigint) from credential_library where public_id = 'vl_______tt1';
select * from finish();
@@ -13,19 +13,19 @@ begin;
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______wvs';
select is(count(*), 4::bigint)
from credential_vault_library_ssh_private_key_mapping_override
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
where library_id in ('vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12');
select is(count(*), 4::bigint)
from credential_vault_library_username_password_mapping_override
from credential_vault_generic_library_username_password_mapping_ovrd
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7');
select is(count(*), 8::bigint)
from credential_vault_library_username_password_domain_mapping_ovrd
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
where library_id in ('vl______wvl13', 'vl______wvl14', 'vl______wvl15', 'vl______wvl16', 'vl______wvl17', 'vl______wvl18', 'vl______wvl19', 'vl______wvl20');
select is(count(*), 8::bigint)
from credential_vault_library_mapping_override
from credential_vault_generic_library_mapping_override
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7', 'vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12');
-- create test vault tokens
@@ -0,0 +1,602 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
-- Tests the refactor around credential_vault_library (and other associated
-- tables/views). It tests that the new structure is setup as expected with all
-- the associated triggers, FKs, constraints, etc. It does not test the refactor
-- where state is present in the database beforehand. For this, see the Go SQL
-- tests in migrations/oss/postgres_99_01_test.go.
--
-- For extra context, see migration folder 99/README.md.
begin;
select plan(97);
-- Redefined credential_vault_library.
select has_table( 'credential_vault_library');
select has_column( 'credential_vault_library', 'public_id');
select col_is_pk( 'credential_vault_library', 'public_id');
select col_not_null('credential_vault_library', 'public_id');
select has_column( 'credential_vault_library', 'store_id');
select col_not_null('credential_vault_library', 'store_id');
select has_column( 'credential_vault_library', 'credential_type');
select col_not_null( 'credential_vault_library', 'credential_type');
select col_default_is('credential_vault_library', 'credential_type', 'unspecified');
select has_column( 'credential_vault_library', 'project_id');
select col_not_null('credential_vault_library', 'project_id');
select has_column( 'credential_vault_library', 'create_time');
select col_not_null('credential_vault_library', 'create_time');
select has_column( 'credential_vault_library', 'update_time');
select col_not_null('credential_vault_library', 'update_time');
select fk_ok('credential_vault_library', ARRAY['public_id', 'store_id', 'credential_type', 'project_id'], 'credential_library', ARRAY['public_id', 'store_id', 'credential_type', 'project_id']);
select fk_ok('credential_vault_library', ARRAY['project_id', 'store_id'], 'credential_vault_store', ARRAY['project_id', 'public_id']);
select has_trigger('credential_vault_library', 'immutable_columns');
select has_trigger('credential_vault_library', 'insert_credential_library_subtype');
select has_trigger('credential_vault_library', 'delete_credential_library_subtype');
select has_trigger('credential_vault_library', 'update_credential_library_table_update_time');
-- Vault generic credential library table.
select has_table('credential_vault_generic_library');
select has_column( 'credential_vault_generic_library', 'public_id');
select col_is_pk('credential_vault_generic_library', 'public_id');
select has_column( 'credential_vault_generic_library', 'store_id');
select has_column( 'credential_vault_generic_library', 'name');
select has_column( 'credential_vault_generic_library', 'description');
select has_column( 'credential_vault_generic_library', 'create_time');
select has_column( 'credential_vault_generic_library', 'update_time');
select has_column( 'credential_vault_generic_library', 'version');
select has_column( 'credential_vault_generic_library', 'vault_path');
select has_column( 'credential_vault_generic_library', 'http_method');
select has_column( 'credential_vault_generic_library', 'http_request_body');
select has_column( 'credential_vault_generic_library', 'credential_type');
select has_column( 'credential_vault_generic_library', 'project_id');
select fk_ok('credential_vault_generic_library', ARRAY['project_id', 'store_id', 'public_id', 'credential_type'], 'credential_vault_library', ARRAY['project_id', 'store_id', 'public_id', 'credential_type']);
select has_trigger('credential_vault_generic_library', 'immutable_columns');
select has_trigger('credential_vault_generic_library', 'insert_deleted_id');
select has_trigger('credential_vault_generic_library', 'insert_credential_vault_library_subtype');
select has_trigger('credential_vault_generic_library', 'update_credential_vault_library_table_update_time');
select has_trigger('credential_vault_generic_library', 'delete_credential_vault_library_subtype');
select has_trigger('credential_vault_generic_library', 'before_insert_credential_vault_library');
select has_trigger('credential_vault_generic_library', 'hst_on_insert');
select has_trigger('credential_vault_generic_library', 'hst_on_update');
select has_trigger('credential_vault_generic_library', 'hst_on_delete');
-- Vault generic deleted table.
select hasnt_table('credential_vault_library_deleted');
select has_table( 'credential_vault_generic_library_deleted');
-- Vault generic history table.
select hasnt_table('credential_vault_library_hst');
select has_table( 'credential_vault_generic_library_hst');
-- Mapping overrides: Base table, UP, UPD and SSH PK.
select hasnt_table('credential_vault_library_mapping_override');
select has_table('credential_vault_generic_library_mapping_override');
select hasnt_table('credential_vault_library_username_password_mapping_override');
select has_table('credential_vault_generic_library_username_password_mapping_ovrd');
select hasnt_table('credential_vault_library_username_password_domain_mapping_ovrd');
select has_table('credential_vault_generic_library_usern_pass_domain_mapping_ovrd');
select hasnt_table('credential_vault_library_ssh_private_key_mapping_override');
select has_table('credential_vault_generic_library_ssh_private_key_mapping_ovrd');
-- Views.
select has_view('credential_vault_library_issue_credentials');
select has_view('whx_credential_dimension_source');
select hasnt_view('credential_vault_generic_hst_aggregate');
select has_view('credential_vault_generic_library_hst_aggregate');
select hasnt_view('credential_vault_library_list_lookup');
select has_view('credential_vault_generic_library_list_lookup');
-- Vault dynamic credentials table.
select fk_ok('credential_vault_credential', 'library_id', 'credential_vault_library', 'public_id');
-- We can be reasonably sure the structure is as intended. Now we insert data
-- to actually test it.
select lives_ok(
$$
insert into iam_scope
(parent_id, type, public_id, name)
values
('global', 'org', 'o_LaIu234Pg6', 'Test Org Scope');
insert into iam_scope
(parent_id, type, public_id, name)
values
('o_LaIu234Pg6', 'project', 'p_31ouN5ldA2', 'Test Project Scope');
insert into credential_vault_store
(public_id, project_id, name, vault_address)
values
('csvlt_5n3A2kJo', 'p_31ouN5ldA2', 'Test Vault Credential Store', 'https://my.vault.instance.local');
insert into credential_vault_generic_library
(public_id, store_id, project_id, name, description, vault_path, http_method, http_request_body)
values
('clvlt_V134iOjA', 'csvlt_5n3A2kJo', 'p_31ouN5ldA2', 'vault_generic1', 'Test vault generic credential library', '/my/vault/path', 'POST', 'my_http_request_body');
insert into credential_vault_generic_library
(public_id, store_id, project_id, credential_type, name, description, vault_path, http_method)
values
('clvlt_j5oK17vG', 'csvlt_5n3A2kJo', 'p_31ouN5ldA2', 'username_password', 'vault_generic2', 'Test vault generic credential library 2', '/my/vault2/path', 'GET');
insert into credential_vault_ssh_cert_library
(public_id, store_id, project_id, name, vault_path, username, key_type, key_bits)
values
('clvsclt_35iWGH12', 'csvlt_5n3A2kJo', 'p_31ouN5ldA2', 'vault_ssh_cert1', '/ssh/issue/mycert', 'myuser', 'ed25519', 0);
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
values ('clvlt_V134iOjA', 'custom_username_attr', 'custom_pw_attr', 'custom_domain_attr');
insert into credential_vault_generic_library_username_password_mapping_ovrd
values ('clvlt_j5oK17vG', 'custom_username_attr', 'custom_pw_attr');
$$
);
-- Assert the data was inserted correctly into the subtype tables.
select row_eq(
$$ -- Query:
select public_id,
store_id,
name,
description,
version,
vault_path,
http_method,
http_request_body,
credential_type,
project_id
from credential_vault_generic_library
where public_id = 'clvlt_V134iOjA';
$$,
row( -- Expect:
'clvlt_V134iOjA'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'vault_generic1'::wt_name,
'Test vault generic credential library'::wt_description,
1::wt_version,
'/my/vault/path'::text,
'POST'::text,
'my_http_request_body'::bytea,
'unspecified'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
select row_eq(
$$ -- Query:
select public_id,
store_id,
name,
description,
version,
vault_path,
http_method,
http_request_body,
credential_type,
project_id
from credential_vault_generic_library
where public_id = 'clvlt_j5oK17vG';
$$,
row( -- Expect:
'clvlt_j5oK17vG'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'vault_generic2'::wt_name,
'Test vault generic credential library 2'::wt_description,
1::wt_version,
'/my/vault2/path'::text,
'GET'::text,
null::bytea,
'username_password'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
select row_eq(
$$ -- Query:
select public_id,
store_id,
name,
version,
vault_path,
username,
key_type,
key_bits,
ttl,
key_id,
critical_options,
extensions,
credential_type,
project_id,
additional_valid_principals
from credential_vault_ssh_cert_library
where public_id = 'clvsclt_35iWGH12';
$$,
row( -- Expect:
'clvsclt_35iWGH12'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'vault_ssh_cert1'::wt_name,
1::wt_version,
'/ssh/issue/mycert'::text,
'myuser'::text,
'ed25519'::text,
0::integer,
null::text,
null::text,
null::bytea,
null::bytea,
'ssh_certificate'::text,
'p_31ouN5ldA2'::wt_public_id,
null::text
)
);
-- Assert that the data was inserted correctly into the base tables
-- (credential_vault_library and credential_library).
select row_eq(
$$ -- Query:
select public_id,
store_id,
credential_type,
project_id
from credential_vault_library
where public_id = 'clvlt_V134iOjA';
$$,
row( -- Expect:
'clvlt_V134iOjA'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'unspecified'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
select row_eq(
$$ -- Query:
select public_id,
store_id,
credential_type,
project_id
from credential_library
where public_id = 'clvlt_V134iOjA';
$$,
row( -- Expect:
'clvlt_V134iOjA'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'unspecified'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
select row_eq(
$$ -- Query:
select public_id,
store_id,
credential_type,
project_id
from credential_vault_library
where public_id = 'clvlt_j5oK17vG';
$$,
row( -- Expect:
'clvlt_j5oK17vG'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'username_password'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
select row_eq(
$$ -- Query:
select public_id,
store_id,
credential_type,
project_id
from credential_library
where public_id = 'clvlt_j5oK17vG';
$$,
row( -- Expect:
'clvlt_j5oK17vG'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'username_password'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
select row_eq(
$$ -- Query:
select public_id,
store_id,
credential_type,
project_id
from credential_vault_library
where public_id = 'clvsclt_35iWGH12';
$$,
row( -- Expect:
'clvsclt_35iWGH12'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'ssh_certificate'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
select row_eq(
$$ -- Query:
select public_id,
store_id,
credential_type,
project_id
from credential_library
where public_id = 'clvsclt_35iWGH12';
$$,
row( -- Expect:
'clvsclt_35iWGH12'::wt_public_id,
'csvlt_5n3A2kJo'::wt_public_id,
'ssh_certificate'::text,
'p_31ouN5ldA2'::wt_public_id
)
);
-- Assert that the data was inserted correctly into the history tables.
select row_eq(
$$
select count(*)
from credential_vault_generic_library_hst
where public_id = 'clvlt_V134iOjA';
$$,
row(1::bigint)
);
select row_eq(
$$
select count(*)
from credential_library_history_base as base
join credential_vault_generic_library_hst as vglh on base.history_id = vglh.history_id
where vglh.public_id = 'clvlt_V134iOjA';
$$,
row(1::bigint)
);
select row_eq(
$$
select count(*)
from credential_vault_generic_library_hst
where public_id = 'clvlt_j5oK17vG';
$$,
row(1::bigint)
);
select row_eq(
$$
select count(*)
from credential_library_history_base as base
join credential_vault_generic_library_hst as vglh on base.history_id = vglh.history_id
where vglh.public_id = 'clvlt_j5oK17vG';
$$,
row(1::bigint)
);
select row_eq(
$$
select count(*)
from credential_vault_ssh_cert_library_hst
where public_id = 'clvsclt_35iWGH12';
$$,
row(1::bigint)
);
select row_eq(
$$
select count(*)
from credential_library_history_base as base
join credential_vault_ssh_cert_library_hst as vsclh on base.history_id = vsclh.history_id
where vsclh.public_id = 'clvsclt_35iWGH12';
$$,
row(1::bigint)
);
-- Assert that the data was inserted correctly into the mapping override
-- tables.
select row_eq(
$$
select count(*)
from credential_vault_generic_library_mapping_override;
$$,
row(2::bigint)
);
select row_eq(
$$
select count(*)
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
where library_id = 'clvlt_V134iOjA';
$$,
row(1::bigint)
);
select row_eq(
$$
select count(*)
from credential_vault_generic_library_username_password_mapping_ovrd
where library_id = 'clvlt_j5oK17vG';
$$,
row(1::bigint)
);
-- Update credential library. Verify that:
-- - The update works.
-- - Version column is incremented.
-- - A new history entry is created both in the base table and subtype table.
select lives_ok(
$$
update credential_vault_generic_library
set
name = 'vault_generic1_updated',
vault_path = '/my/updated/vault/path'
where public_id = 'clvlt_V134iOjA';
$$
);
select row_eq(
$$ -- Query:
select public_id,
name,
version,
vault_path
from credential_vault_generic_library
where public_id = 'clvlt_V134iOjA';
$$,
row( -- Expect:
'clvlt_V134iOjA'::wt_public_id,
'vault_generic1_updated'::wt_name,
2::wt_version,
'/my/updated/vault/path'::text
)
);
select row_eq(
$$
select count(*)
from credential_vault_generic_library_hst
where public_id = 'clvlt_V134iOjA';
$$,
row(2::bigint)
);
select row_eq(
$$
select count(*)
from credential_library_history_base as base
join credential_vault_generic_library_hst as vglh on base.history_id = vglh.history_id
where vglh.public_id = 'clvlt_V134iOjA';
$$,
row(2::bigint)
);
-- Delete credential library. Verify that:
-- - The delete works.
-- - The deleted credential library id is inserted into the deleted table.
-- - The deleted credential library row is deleted from the base tables.
-- - Mapping overrides that reference this credential library are
-- automatically deleted.
select lives_ok(
$$
delete from credential_vault_generic_library
where public_id = 'clvlt_j5oK17vG';
$$
);
select row_eq(
$$
select count(*)
from credential_vault_generic_library_deleted
where public_id = 'clvlt_j5oK17vG';
$$,
row(1::bigint)
);
select row_eq(
$$
select count(*)
from credential_vault_library
where public_id = 'clvlt_j5oK17vG';
$$,
row(0::bigint)
);
select row_eq(
$$
select count(*)
from credential_library
where public_id = 'clvlt_j5oK17vG';
$$,
row(0::bigint)
);
select row_eq(
$$
select count(*)
from credential_vault_generic_library_username_password_mapping_ovrd
where library_id = 'clvlt_j5oK17vG';
$$,
row(0::bigint)
);
select row_eq(
$$
select count(*)
from credential_vault_generic_library_mapping_override
where library_id = 'clvlt_j5oK17vG';
$$,
row(0::bigint)
);
-- Check data in views.
select results_eq(
$$
select credential_library_id,
credential_library_name
from whx_credential_dimension_source
order by credential_library_name;
$$,
$$
values
('cvl_______g1'::wt_public_id, 'green vault library'),
('cvl__ssh__g1'::wt_public_id, 'green vault ssh library')
$$
);
select results_eq(
$$
select public_id,
name
from credential_vault_library_issue_credentials
order by name;
$$,
$$
values
( 'cvl__ldap_b1'::wt_public_id, 'blue vault ldap library'::wt_name),
( 'cvl_______b1'::wt_public_id, 'blue vault library'::wt_name),
( 'cvl__ssh__b1'::wt_public_id, 'blue vault ssh library'::wt_name),
( 'cvl__ldap_g1'::wt_public_id, 'green vault ldap library'::wt_name),
( 'cvl_______g1'::wt_public_id, 'green vault library'::wt_name),
( 'cvl__ssh__g1'::wt_public_id, 'green vault ssh library'::wt_name),
( 'cvl__ldap_r1'::wt_public_id, 'red vault ldap library'::wt_name),
( 'cvl_______r1'::wt_public_id, 'red vault library'::wt_name),
( 'cvl__ssh__r1'::wt_public_id, 'red vault ssh library'::wt_name),
( 'clvlt_V134iOjA'::wt_public_id, 'vault_generic1_updated'::wt_name),
('clvsclt_35iWGH12'::wt_public_id, 'vault_ssh_cert1'::wt_name);
$$
);
select results_eq(
$$
select public_id,
name
from credential_vault_generic_library_list_lookup
order by name;
$$,
$$
values
( 'cvl_______b1'::wt_public_id, 'blue vault library'::wt_name),
( 'cvl_______g1'::wt_public_id, 'green vault library'::wt_name),
( 'cvl_______r1'::wt_public_id, 'red vault library'::wt_name),
( 'clvlt_V134iOjA'::wt_public_id, 'vault_generic1_updated'::wt_name);
$$
);
select results_eq(
$$
select public_id,
name
from credential_vault_generic_library_hst_aggregate
order by name;
$$,
$$
values
('cvl_______g1'::wt_public_id, 'green vault library'::wt_name)
$$
);
select * from finish();
rollback;
@@ -3,10 +3,10 @@
-- credential_vault_library_ssh_private_key_mapping_override tests:
-- the following triggers
-- insert_credential_vault_library_mapping_override_subtype
-- delete_credential_vault_library_mapping_override_subtype
-- insert_credential_vault_generic_library_mapping_override_subtyp
-- delete_credential_vault_generic_library_mapping_override_subtyp
-- and the following view
-- credential_vault_library_list_lookup
-- credential_vault_generic_library_list_lookup
begin;
select plan(11);
@@ -14,16 +14,16 @@ begin;
-- validate the setup data
select is(count(*), 4::bigint)
from credential_vault_library_ssh_private_key_mapping_override
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
where library_id in ('vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12');
select is(count(*), 4::bigint)
from credential_vault_library_mapping_override
from credential_vault_generic_library_mapping_override
where library_id in ('vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12');
prepare select_libraries as
select public_id::text, credential_type::text, username_attribute::text, private_key_attribute::text, private_key_passphrase_attribute::text
from credential_vault_library_list_lookup
from credential_vault_generic_library_list_lookup
where public_id in ('vl______wvl2', 'vl______wvl8', 'vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12')
order by public_id;
@@ -39,28 +39,28 @@ begin;
);
-- validate the insert triggers
select is(count(*), 0::bigint) from credential_vault_library_ssh_private_key_mapping_override where library_id = 'vl______wvl8';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl8';
select is(count(*), 0::bigint) from credential_vault_generic_library_ssh_private_key_mapping_ovrd where library_id = 'vl______wvl8';
select is(count(*), 0::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl8';
prepare insert_cvl_ssh_private_key_mapping_override as
insert into credential_vault_library_ssh_private_key_mapping_override
insert into credential_vault_generic_library_ssh_private_key_mapping_ovrd
(library_id, username_attribute, private_key_attribute)
values
('vl______wvl8', 'my_username', 'my_private_key');
select lives_ok('insert_cvl_ssh_private_key_mapping_override');
select is(count(*), 1::bigint) from credential_vault_library_ssh_private_key_mapping_override where library_id = 'vl______wvl8';
select is(count(*), 1::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl8';
select is(count(*), 1::bigint) from credential_vault_generic_library_ssh_private_key_mapping_ovrd where library_id = 'vl______wvl8';
select is(count(*), 1::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl8';
-- validate the delete triggers
prepare delete_cvl_ssh_private_key_mapping_override as
delete
from credential_vault_library_ssh_private_key_mapping_override
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
where library_id = 'vl______wvl8';
select lives_ok('delete_cvl_ssh_private_key_mapping_override');
select is(count(*), 0::bigint) from credential_vault_library_ssh_private_key_mapping_override where library_id = 'vl______wvl8';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl8';
select is(count(*), 0::bigint) from credential_vault_generic_library_ssh_private_key_mapping_ovrd where library_id = 'vl______wvl8';
select is(count(*), 0::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl8';
select * from finish();
rollback;
@@ -3,10 +3,10 @@
-- credential_vault_library_username_password_domain_mapping_ovrd tests:
-- the following triggers
-- insert_credential_vault_library_mapping_override_subtype
-- delete_credential_vault_library_mapping_override_subtype
-- insert_credential_vault_generic_library_mapping_override_subtyp
-- delete_credential_vault_generic_library_mapping_override_subtyp
-- and the following view
-- credential_vault_library_list_lookup
-- credential_vault_generic_library_list_lookup
begin;
@@ -15,16 +15,16 @@ begin;
select is(count(*), 8::bigint)
from credential_vault_library_username_password_domain_mapping_ovrd
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
where library_id in ('vl______wvl13', 'vl______wvl14', 'vl______wvl15', 'vl______wvl16', 'vl______wvl17', 'vl______wvl18', 'vl______wvl19', 'vl______wvl20');
select is(count(*), 8::bigint)
from credential_vault_library_mapping_override
from credential_vault_generic_library_mapping_override
where library_id in ('vl______wvl13', 'vl______wvl14', 'vl______wvl15', 'vl______wvl16', 'vl______wvl17', 'vl______wvl18', 'vl______wvl19', 'vl______wvl20');
prepare select_libraries as
select public_id::text, credential_type::text, username_attribute::text, password_attribute::text, domain_attribute::text
from credential_vault_library_list_lookup
from credential_vault_generic_library_list_lookup
where public_id in ('vl______wvl2', 'vl______wvl21', 'vl______wvl13', 'vl______wvl14', 'vl______wvl15', 'vl______wvl16', 'vl______wvl17', 'vl______wvl18', 'vl______wvl19', 'vl______wvl20')
order by public_id;
@@ -45,30 +45,30 @@ begin;
-- validate insert triggers
select is(count(*), 0::bigint) from credential_vault_library_username_password_domain_mapping_ovrd where library_id = 'vl______wvl21';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl21';
select is(count(*), 0::bigint) from credential_vault_generic_library_usern_pass_domain_mapping_ovrd where library_id = 'vl______wvl21';
select is(count(*), 0::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl21';
prepare insert_cvl_username_password_mapping_override as
insert into credential_vault_library_username_password_domain_mapping_ovrd
insert into credential_vault_generic_library_usern_pass_domain_mapping_ovrd
(library_id,username_attribute, password_attribute, domain_attribute)
values
('vl______wvl21', 'my_username', 'my_password', 'my_domain');
select lives_ok('insert_cvl_username_password_mapping_override');
select is(count(*), 1::bigint) from credential_vault_library_username_password_domain_mapping_ovrd where library_id = 'vl______wvl21';
select is(count(*), 1::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl21';
select is(count(*), 1::bigint) from credential_vault_generic_library_usern_pass_domain_mapping_ovrd where library_id = 'vl______wvl21';
select is(count(*), 1::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl21';
-- validate delete triggers
prepare delete_cvl_username_password_domain_mapping_ovrd as
delete
from credential_vault_library_username_password_domain_mapping_ovrd
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
where library_id = 'vl______wvl21';
select lives_ok('delete_cvl_username_password_domain_mapping_ovrd');
select is(count(*), 0::bigint) from credential_vault_library_username_password_domain_mapping_ovrd where library_id = 'vl______wvl21';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl21';
select is(count(*), 0::bigint) from credential_vault_generic_library_usern_pass_domain_mapping_ovrd where library_id = 'vl______wvl21';
select is(count(*), 0::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl21';
select * from finish();
@@ -3,10 +3,10 @@
-- credential_vault_library_username_password_mapping_override tests:
-- the following triggers
-- insert_credential_vault_library_mapping_override_subtype
-- delete_credential_vault_library_mapping_override_subtype
-- insert_credential_vault_generic_library_mapping_override_subtyp
-- delete_credential_vault_generic_library_mapping_override_subtyp
-- and the following view
-- credential_vault_library_list_lookup
-- credential_vault_generic_library_list_lookup
begin;
select plan(11);
@@ -14,16 +14,16 @@ begin;
-- validate the setup data
select is(count(*), 4::bigint)
from credential_vault_library_username_password_mapping_override
from credential_vault_generic_library_username_password_mapping_ovrd
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7');
select is(count(*), 4::bigint)
from credential_vault_library_mapping_override
from credential_vault_generic_library_mapping_override
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7');
prepare select_libraries as
select public_id::text, credential_type::text, username_attribute::text, password_attribute::text
from credential_vault_library_list_lookup
from credential_vault_generic_library_list_lookup
where public_id in ('vl______wvl2', 'vl______wvl3', 'vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7')
order by public_id;
@@ -39,28 +39,28 @@ begin;
);
-- validate the insert triggers
select is(count(*), 0::bigint) from credential_vault_library_username_password_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_generic_library_username_password_mapping_ovrd where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl3';
prepare insert_cvl_username_password_mapping_override as
insert into credential_vault_library_username_password_mapping_override
insert into credential_vault_generic_library_username_password_mapping_ovrd
(library_id, username_attribute, password_attribute)
values
('vl______wvl3', 'my_username', 'my_password');
select lives_ok('insert_cvl_username_password_mapping_override');
select is(count(*), 1::bigint) from credential_vault_library_username_password_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 1::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 1::bigint) from credential_vault_generic_library_username_password_mapping_ovrd where library_id = 'vl______wvl3';
select is(count(*), 1::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl3';
-- validate the delete triggers
prepare delete_cvl_username_password_mapping_override as
delete
from credential_vault_library_username_password_mapping_override
from credential_vault_generic_library_username_password_mapping_ovrd
where library_id = 'vl______wvl3';
select lives_ok('delete_cvl_username_password_mapping_override');
select is(count(*), 0::bigint) from credential_vault_library_username_password_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_generic_library_username_password_mapping_ovrd where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_generic_library_mapping_override where library_id = 'vl______wvl3';
select * from finish();
rollback;
@@ -2,7 +2,7 @@
-- SPDX-License-Identifier: BUSL-1.1
begin;
select plan(13);
select plan(16);
-- Verify the trigger functions exist and are declared properly
select has_function('insert_credential_library_history_subtype');
@@ -13,18 +13,23 @@ begin;
select volatility_is('delete_credential_library_history_subtype', 'volatile');
select isnt_strict('delete_credential_library_history_subtype');
select has_trigger('credential_vault_library_hst', 'insert_credential_library_history_subtype');
select has_trigger('credential_vault_library_hst', 'delete_credential_library_history_subtype');
select fk_ok('credential_vault_library_hst', 'history_id', 'credential_library_history_base' , 'history_id');
select has_trigger('credential_vault_generic_library_hst', 'insert_credential_library_history_subtype');
select has_trigger('credential_vault_generic_library_hst', 'delete_credential_library_history_subtype');
select fk_ok('credential_vault_generic_library_hst', 'history_id', 'credential_library_history_base' , 'history_id');
select has_trigger('credential_vault_ssh_cert_library_hst', 'insert_credential_library_history_subtype');
select has_trigger('credential_vault_ssh_cert_library_hst', 'delete_credential_library_history_subtype');
select fk_ok('credential_vault_ssh_cert_library_hst', 'history_id', 'credential_library_history_base' , 'history_id');
select has_trigger('credential_vault_ldap_library_hst', 'insert_credential_library_history_subtype');
select has_trigger('credential_vault_ldap_library_hst', 'delete_credential_library_history_subtype');
select fk_ok('credential_vault_ldap_library_hst', 'history_id', 'credential_library_history_base' , 'history_id');
select results_eq(
'select '
'(select count(*) from credential_vault_library_hst) + '
'(select count(*) from credential_vault_ssh_cert_library_hst)',
'(select count(*) from credential_vault_generic_library_hst) + '
'(select count(*) from credential_vault_ssh_cert_library_hst) + '
'(select count(*) from credential_vault_ldap_library_hst)',
'select count(*) from credential_library_history_base'
);
@@ -2,14 +2,18 @@
-- SPDX-License-Identifier: BUSL-1.1
begin;
select plan(9);
select plan(12);
-- Verify the trigger functions exist and are declared properly
select has_function('update_credential_library_table_update_time');
select volatility_is('update_credential_library_table_update_time', 'volatile');
select isnt_strict('update_credential_library_table_update_time');
select has_trigger('credential_vault_library', 'update_credential_library_table_update_time');
select has_trigger('credential_vault_ssh_cert_library', 'update_credential_library_table_update_time');
select has_function('update_credential_vault_library_table_update_time');
select volatility_is('update_credential_vault_library_table_update_time', 'volatile');
select isnt_strict('update_credential_vault_library_table_update_time');
select has_trigger('credential_vault_generic_library', 'update_credential_vault_library_table_update_time');
select has_trigger('credential_vault_ssh_cert_library', 'update_credential_vault_library_table_update_time');
select has_index('credential_library', 'credential_library_create_time_public_id_idx', array['create_time', 'public_id']);
select has_index('credential_library', 'credential_library_update_time_public_id_idx', array['update_time', 'public_id']);
@@ -17,14 +21,14 @@ begin;
-- when an insert into a subtype table happens works, we check that the
-- create time of the entries in both tables match
prepare credential_library_create_time as select create_time from credential_library where public_id='cvl_______b1';
prepare credential_vault_library_create_time as select create_time from credential_vault_library where public_id='cvl_______b1';
prepare credential_vault_library_create_time as select create_time from credential_vault_generic_library where public_id='cvl_______b1';
select results_eq('credential_library_create_time','credential_vault_library_create_time');
-- To test the trigger that updates the update_time of the base table,
-- we update one of the existing creds and check that the base table
-- entry has the same update_time as the subtype one.
update credential_vault_library set name='blue black vault library' where public_id='cvl_______b1';
update credential_vault_generic_library set name='blue black vault library' where public_id='cvl_______b1';
prepare credential_library_update_time as select update_time from credential_library where public_id='cvl_______b1';
prepare credential_vault_library_update_time as select update_time from credential_vault_library where public_id='cvl_______b1';
prepare credential_vault_library_update_time as select update_time from credential_vault_generic_library where public_id='cvl_______b1';
select results_eq('credential_library_update_time','credential_vault_library_update_time');
select * from finish();
@@ -5,7 +5,7 @@ begin;
select plan(31);
select has_table('recording_dynamic_credential');
select has_view('credential_vault_library_hst_aggregate', 'view for aggregate vault library history info does not exist');
select has_view('credential_vault_generic_library_hst_aggregate', 'view for aggregate vault generic library history info does not exist');
select has_view('credential_vault_ssh_cert_library_hst_aggregate', 'view for aggregate vault ssh cert library history info does not exist');
-- tests a fk column referencing a history table
@@ -55,7 +55,7 @@ begin;
prepare select_recording_vault_libraries as
select recording_id::text, public_id::text, store_public_id::text, purposes::text
from credential_vault_library_hst_aggregate
from credential_vault_generic_library_hst_aggregate
where recording_id = 'sr1_____cora'
order by public_id;
@@ -46,7 +46,7 @@ begin;
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- change the crediential for the target
update credential_vault_library set vault_path = '/secrets/tcp/admin' where public_id = 'vl______wvl1';
update credential_vault_generic_library set vault_path = '/secrets/tcp/admin' where public_id = 'vl______wvl1';
-- start another session, should result in a new credential dimension
insert into session
@@ -80,7 +80,7 @@ begin;
select is(count(*), 3::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- change the crediential again for the target
update credential_vault_library set vault_path = '/secrets/tcp/user' where vault_path = '/secrets/tcp/admin';
update credential_vault_generic_library set vault_path = '/secrets/tcp/user' where vault_path = '/secrets/tcp/admin';
-- start another session, should result in a one new credential dimensions since one changed
insert into session
@@ -101,7 +101,7 @@ begin;
-- remove all credentials from the target
-- then test creating a session
delete from credential_vault_library;
delete from credential_vault_generic_library;
insert into session
( project_id, target_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
@@ -69,7 +69,7 @@ begin;
join wh_session_accumulating_fact on wh_session_accumulating_fact.session_id = wh_session_connection_accumulating_fact.session_id
where wh_session_connection_accumulating_fact.session_id = 's1____walter';
update credential_vault_library set vault_path = '/secrets/tcp/user' where public_id = 'vl______wvl2';
update credential_vault_generic_library set vault_path = '/secrets/tcp/user' where public_id = 'vl______wvl2';
insert into session
( project_id, target_id, user_id, auth_token_id, certificate, endpoint, public_id)
@@ -77,6 +77,11 @@ message CredentialLibrary {
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "vault-generic"
];
VaultLdapCredentialLibraryAttributes vault_ldap_credential_library_attributes = 104 [
(google.api.field_visibility).restriction = "INTERNAL",
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "vault-ldap"
];
}
// Output only. The available actions on this resource for this user.
@@ -210,3 +215,15 @@ message VaultSSHCertificateCredentialLibraryAttributes {
}
]; // @gotags: `class:"public"`
}
// The attributes of a vault LDAP Credential Library.
message VaultLdapCredentialLibraryAttributes {
// The path in Vault to request credentials from.
google.protobuf.StringValue path = 10 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.path"
that: "VaultPath"
}
]; // @gotags: `class:"public"`
}
@@ -535,3 +535,52 @@ message SshPrivateKeyOverride {
// @inject_tag: `gorm:"default:null"`
string private_key_passphrase_attribute = 4;
}
message LdapCredentialLibrary {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
string public_id = 1;
// create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 2;
// update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp update_time = 3;
// name is optional. If set, it must be unique within project_id.
// @inject_tag: `gorm:"default:null"`
string name = 4 [(custom_options.v1.mask_mapping) = {
this: "Name"
that: "name"
}];
// description is optional.
// @inject_tag: `gorm:"default:null"`
string description = 5 [(custom_options.v1.mask_mapping) = {
this: "Description"
that: "description"
}];
// store_id of the owning vault credential store.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string store_id = 6;
// version allows optimistic locking of the resource.
// @inject_tag: `gorm:"default:null"`
uint32 version = 7;
// vault_path is the path in Vault to request credentials from.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string vault_path = 8 [(custom_options.v1.mask_mapping) = {
this: "VaultPath"
that: "attributes.path"
}];
// credential_type is always ssh_certificate
// @inject_tag: `gorm:"default:null"`
string credential_type = 9;
}
@@ -18,6 +18,17 @@ function create_vault_generic_library() {
create vault-generic $@
}
function create_vault_ldap_library() {
boundary credential-libraries \
create vault-ldap $@
}
function update_vault_ldap_library() {
boundary credential-libraries \
update vault-ldap $@
}
function create_vault_library() {
boundary credential-libraries \
create vault $@
@@ -52,7 +52,7 @@ export NEW_VAULT_LIB="test_vault"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault-generic libary" {
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault-generic library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
@@ -106,7 +106,7 @@ export NEW_VAULT_LIB="test_vault"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault-ssh-certificate libary" {
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault-ssh-certificate library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
@@ -587,6 +587,69 @@ export NEW_VAULT_LIB="test_vault"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can create $NEW_VAULT_LIB vault-ldap library in credential store $NEW_STORE" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_ldap_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /ldap/static-cred/einstein \
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault-ldap library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_ldap_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /ldap/static-cred/einstein \
echo "$output"
[ "$status" -eq 1 ]
}
@test "boundary/credential-libraries: can update $NEW_VAULT_LIB vault-ldap library description" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run update_vault_ldap_library -id $clid -description hellothere
echo "$output"
[ "$status" -eq 0 ]
run read_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
got=$(echo "$output")
run field_eq "$got" ".item.attributes.description" "null"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can read $NEW_VAULT_LIB vault-ldap library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run read_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can delete $NEW_VAULT_LIB vault-ldaplibrary" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run delete_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
# Note, deleting the cred store will revoke the vault token
@test "boundary/credential-stores: cleanup can delete $NEW_STORE vault store" {
skip_if_no_vault
@@ -58,6 +58,7 @@ type CredentialLibrary struct {
// *CredentialLibrary_VaultCredentialLibraryAttributes
// *CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes
// *CredentialLibrary_VaultGenericCredentialLibraryAttributes
// *CredentialLibrary_VaultLdapCredentialLibraryAttributes
Attrs isCredentialLibrary_Attrs `protobuf_oneof:"attrs"`
// Output only. The available actions on this resource for this user.
AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty" class:"public"` // @gotags: `class:"public"`
@@ -205,6 +206,15 @@ func (x *CredentialLibrary) GetVaultGenericCredentialLibraryAttributes() *VaultC
return nil
}
func (x *CredentialLibrary) GetVaultLdapCredentialLibraryAttributes() *VaultLdapCredentialLibraryAttributes {
if x != nil {
if x, ok := x.Attrs.(*CredentialLibrary_VaultLdapCredentialLibraryAttributes); ok {
return x.VaultLdapCredentialLibraryAttributes
}
}
return nil
}
func (x *CredentialLibrary) GetAuthorizedActions() []string {
if x != nil {
return x.AuthorizedActions
@@ -247,6 +257,10 @@ type CredentialLibrary_VaultGenericCredentialLibraryAttributes struct {
VaultGenericCredentialLibraryAttributes *VaultCredentialLibraryAttributes `protobuf:"bytes,103,opt,name=vault_generic_credential_library_attributes,json=vaultGenericCredentialLibraryAttributes,proto3,oneof"`
}
type CredentialLibrary_VaultLdapCredentialLibraryAttributes struct {
VaultLdapCredentialLibraryAttributes *VaultLdapCredentialLibraryAttributes `protobuf:"bytes,104,opt,name=vault_ldap_credential_library_attributes,json=vaultLdapCredentialLibraryAttributes,proto3,oneof"`
}
func (*CredentialLibrary_Attributes) isCredentialLibrary_Attrs() {}
func (*CredentialLibrary_VaultCredentialLibraryAttributes) isCredentialLibrary_Attrs() {}
@@ -256,6 +270,8 @@ func (*CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes) isCrede
func (*CredentialLibrary_VaultGenericCredentialLibraryAttributes) isCredentialLibrary_Attrs() {}
func (*CredentialLibrary_VaultLdapCredentialLibraryAttributes) isCredentialLibrary_Attrs() {}
// The attributes of a vault typed Credential Library.
type VaultCredentialLibraryAttributes struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -438,11 +454,57 @@ func (x *VaultSSHCertificateCredentialLibraryAttributes) GetAdditionalValidPrinc
return nil
}
// The attributes of a vault LDAP Credential Library.
type VaultLdapCredentialLibraryAttributes struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The path in Vault to request credentials from.
Path *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=path,proto3" json:"path,omitempty" class:"public"` // @gotags: `class:"public"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *VaultLdapCredentialLibraryAttributes) Reset() {
*x = VaultLdapCredentialLibraryAttributes{}
mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *VaultLdapCredentialLibraryAttributes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VaultLdapCredentialLibraryAttributes) ProtoMessage() {}
func (x *VaultLdapCredentialLibraryAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VaultLdapCredentialLibraryAttributes.ProtoReflect.Descriptor instead.
func (*VaultLdapCredentialLibraryAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescGZIP(), []int{3}
}
func (x *VaultLdapCredentialLibraryAttributes) GetPath() *wrapperspb.StringValue {
if x != nil {
return x.Path
}
return nil
}
var File_controller_api_resources_credentiallibraries_v1_credential_library_proto protoreflect.FileDescriptor
const file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc = "" +
"\n" +
"Hcontroller/api/resources/credentiallibraries/v1/credential_library.proto\x12/controller.api.resources.credentiallibraries.v1\x1a.controller/api/resources/scopes/v1/scope.proto\x1a*controller/custom_options/v1/options.proto\x1a\x1bgoogle/api/visibility.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xae\v\n" +
"Hcontroller/api/resources/credentiallibraries/v1/credential_library.proto\x12/controller.api.resources.credentiallibraries.v1\x1a.controller/api/resources/scopes/v1/scope.proto\x1a*controller/custom_options/v1/options.proto\x1a\x1bgoogle/api/visibility.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\x84\r\n" +
"\x11CredentialLibrary\x12\x0e\n" +
"\x02id\x18\n" +
" \x01(\tR\x02id\x120\n" +
@@ -464,7 +526,10 @@ const file_controller_api_resources_credentiallibraries_v1_credential_library_pr
"3vault_ssh_certificate_credential_library_attributes\x18f \x01(\v2_.controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributesB-\xa0\xda)\x01\x9a\xe3)\x15vault-ssh-certificate\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R.vaultSshCertificateCredentialLibraryAttributes\x12\xd8\x01\n" +
"+vault_generic_credential_library_attributes\x18g \x01(\v2Q.controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributesB%\xa0\xda)\x01\x9a\xe3)\rvault-generic\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R'vaultGenericCredentialLibraryAttributes\x12/\n" +
"\x12\bINTERNALH\x00R'vaultGenericCredentialLibraryAttributes\x12\xd3\x01\n" +
"(vault_ldap_credential_library_attributes\x18h \x01(\v2U.controller.api.resources.credentiallibraries.v1.VaultLdapCredentialLibraryAttributesB\"\xa0\xda)\x01\x9a\xe3)\n" +
"vault-ldap\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R$vaultLdapCredentialLibraryAttributes\x12/\n" +
"\x12authorized_actions\x18\xac\x02 \x03(\tR\x12authorized_actions\x12/\n" +
"\x0fcredential_type\x18\xb6\x02 \x01(\tB\x04\xa0\xda)\x01R\x0fcredential_type\x12b\n" +
"\x1ccredential_mapping_overrides\x18\xc0\x02 \x01(\v2\x17.google.protobuf.StructB\x04\xa0\xda)\x01R\x1ccredential_mapping_overridesB\a\n" +
@@ -507,7 +572,11 @@ const file_controller_api_resources_credentiallibraries_v1_credential_library_pr
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a=\n" +
"\x0fExtensionsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01BhZfgithub.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries;credentiallibrariesb\x06proto3"
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"~\n" +
"$VaultLdapCredentialLibraryAttributes\x12V\n" +
"\x04path\x18\n" +
" \x01(\v2\x1c.google.protobuf.StringValueB$\xa0\xda)\x01\xc2\xdd)\x1c\n" +
"\x0fattributes.path\x12\tVaultPathR\x04pathBhZfgithub.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries;credentiallibrariesb\x06proto3"
var (
file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescOnce sync.Once
@@ -521,47 +590,50 @@ func file_controller_api_resources_credentiallibraries_v1_credential_library_pro
return file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescData
}
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_goTypes = []any{
(*CredentialLibrary)(nil), // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary
(*VaultCredentialLibraryAttributes)(nil), // 1: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes
(*VaultSSHCertificateCredentialLibraryAttributes)(nil), // 2: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes
nil, // 3: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
nil, // 4: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
(*scopes.ScopeInfo)(nil), // 5: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 6: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 8: google.protobuf.Struct
(*wrapperspb.UInt32Value)(nil), // 9: google.protobuf.UInt32Value
(*VaultLdapCredentialLibraryAttributes)(nil), // 3: controller.api.resources.credentiallibraries.v1.VaultLdapCredentialLibraryAttributes
nil, // 4: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
nil, // 5: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
(*scopes.ScopeInfo)(nil), // 6: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 7: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 9: google.protobuf.Struct
(*wrapperspb.UInt32Value)(nil), // 10: google.protobuf.UInt32Value
}
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_depIdxs = []int32{
5, // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
6, // 1: controller.api.resources.credentiallibraries.v1.CredentialLibrary.name:type_name -> google.protobuf.StringValue
6, // 2: controller.api.resources.credentiallibraries.v1.CredentialLibrary.description:type_name -> google.protobuf.StringValue
7, // 3: controller.api.resources.credentiallibraries.v1.CredentialLibrary.created_time:type_name -> google.protobuf.Timestamp
7, // 4: controller.api.resources.credentiallibraries.v1.CredentialLibrary.updated_time:type_name -> google.protobuf.Timestamp
8, // 5: controller.api.resources.credentiallibraries.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
6, // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
7, // 1: controller.api.resources.credentiallibraries.v1.CredentialLibrary.name:type_name -> google.protobuf.StringValue
7, // 2: controller.api.resources.credentiallibraries.v1.CredentialLibrary.description:type_name -> google.protobuf.StringValue
8, // 3: controller.api.resources.credentiallibraries.v1.CredentialLibrary.created_time:type_name -> google.protobuf.Timestamp
8, // 4: controller.api.resources.credentiallibraries.v1.CredentialLibrary.updated_time:type_name -> google.protobuf.Timestamp
9, // 5: controller.api.resources.credentiallibraries.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
1, // 6: controller.api.resources.credentiallibraries.v1.CredentialLibrary.vault_credential_library_attributes:type_name -> controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes
2, // 7: controller.api.resources.credentiallibraries.v1.CredentialLibrary.vault_ssh_certificate_credential_library_attributes:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes
1, // 8: controller.api.resources.credentiallibraries.v1.CredentialLibrary.vault_generic_credential_library_attributes:type_name -> controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes
8, // 9: controller.api.resources.credentiallibraries.v1.CredentialLibrary.credential_mapping_overrides:type_name -> google.protobuf.Struct
6, // 10: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
6, // 11: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_method:type_name -> google.protobuf.StringValue
6, // 12: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_request_body:type_name -> google.protobuf.StringValue
6, // 13: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
6, // 14: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.username:type_name -> google.protobuf.StringValue
6, // 15: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_type:type_name -> google.protobuf.StringValue
9, // 16: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_bits:type_name -> google.protobuf.UInt32Value
6, // 17: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ttl:type_name -> google.protobuf.StringValue
6, // 18: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_id:type_name -> google.protobuf.StringValue
3, // 19: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.critical_options:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
4, // 20: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.extensions:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
6, // 21: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.additional_valid_principals:type_name -> google.protobuf.StringValue
22, // [22:22] is the sub-list for method output_type
22, // [22:22] is the sub-list for method input_type
22, // [22:22] is the sub-list for extension type_name
22, // [22:22] is the sub-list for extension extendee
0, // [0:22] is the sub-list for field type_name
3, // 9: controller.api.resources.credentiallibraries.v1.CredentialLibrary.vault_ldap_credential_library_attributes:type_name -> controller.api.resources.credentiallibraries.v1.VaultLdapCredentialLibraryAttributes
9, // 10: controller.api.resources.credentiallibraries.v1.CredentialLibrary.credential_mapping_overrides:type_name -> google.protobuf.Struct
7, // 11: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
7, // 12: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_method:type_name -> google.protobuf.StringValue
7, // 13: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_request_body:type_name -> google.protobuf.StringValue
7, // 14: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
7, // 15: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.username:type_name -> google.protobuf.StringValue
7, // 16: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_type:type_name -> google.protobuf.StringValue
10, // 17: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_bits:type_name -> google.protobuf.UInt32Value
7, // 18: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ttl:type_name -> google.protobuf.StringValue
7, // 19: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_id:type_name -> google.protobuf.StringValue
4, // 20: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.critical_options:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
5, // 21: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.extensions:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
7, // 22: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.additional_valid_principals:type_name -> google.protobuf.StringValue
7, // 23: controller.api.resources.credentiallibraries.v1.VaultLdapCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
24, // [24:24] is the sub-list for method output_type
24, // [24:24] is the sub-list for method input_type
24, // [24:24] is the sub-list for extension type_name
24, // [24:24] is the sub-list for extension extendee
0, // [0:24] is the sub-list for field type_name
}
func init() { file_controller_api_resources_credentiallibraries_v1_credential_library_proto_init() }
@@ -574,6 +646,7 @@ func file_controller_api_resources_credentiallibraries_v1_credential_library_pro
(*CredentialLibrary_VaultCredentialLibraryAttributes)(nil),
(*CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes)(nil),
(*CredentialLibrary_VaultGenericCredentialLibraryAttributes)(nil),
(*CredentialLibrary_VaultLdapCredentialLibraryAttributes)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -581,7 +654,7 @@ func file_controller_api_resources_credentiallibraries_v1_credential_library_pro
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc), len(file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
+2
View File
@@ -43,6 +43,7 @@ database-up:
-e POSTGRES_PASSWORD=boundary \
-e POSTGRES_USER=boundary \
-e POSTGRES_DB=boundary \
-e TEST_DATABASE_UP_WITH_OP=$(TEST_DATABASE_UP_WITH_OP) \
-e PGDATA=/pgdata \
--mount type=tmpfs,destination=/pgdata \
-v "$(CWD)/../../../internal/db/schema/migrations":/migrations \
@@ -54,6 +55,7 @@ database-up:
@echo "Test database available at: 127.0.0.1:$(TEST_DB_PORT)"
@echo "For database logs run:"
@echo " docker logs $(TEST_CONTAINER_NAME)"
clean:
docker stop $(TEST_CONTAINER_NAME) || true
docker rm -v $(TEST_CONTAINER_NAME) || true
+8
View File
@@ -62,3 +62,11 @@ done
psql -v "ON_ERROR_STOP=1" --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -q <<EOSQL
update pg_database set datistemplate = true, datallowconn = false where datname = 'boundary_template';
EOSQL
# Create database from template
if [ -n "$TEST_DATABASE_UP_WITH_OP" ]; then
psql -v "ON_ERROR_STOP=1" --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -q <<EOSQL
create database op template boundary_template owner ${POSTGRES_USER};
EOSQL
echo "Created operational database 'op' based on boundary_template"
fi
+4 -2
View File
@@ -6,12 +6,14 @@ package boundary
import (
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/targets"
)
// ConnectCliOutput parses the json response from running `boundary connect`
type ConnectCliOutput struct {
Port int `json:"port"`
Address string `json:"address"`
Port int `json:"port"`
Address string `json:"address"`
Credentials []*targets.SessionCredential `json:"credentials"`
}
// AuthenticateCliOutput parses the json response from running `boundary authenticate`
@@ -333,6 +333,54 @@ func CreateStaticCredentialPasswordApi(t testing.TB, ctx context.Context, client
return credentialId, nil
}
func CreateVaultLdapCredentialLibraryApi(t testing.TB, ctx context.Context, client *api.Client, credentialStoreId string, vaultPath string) (string, error) {
name, err := base62.Random(16)
if err != nil {
return "", err
}
res, err := credentiallibraries.NewClient(client).Create(ctx, "vault-ldap", credentialStoreId,
credentiallibraries.WithName(fmt.Sprintf("e2e vault credential library %s", name)),
credentiallibraries.WithVaultLdapCredentialLibraryPath(vaultPath),
)
if err != nil {
return "", err
}
return res.GetItem().Id, nil
}
func CreateVaultLdapCredentialLibraryCli(t testing.TB, ctx context.Context, credentialStoreId string, vaultPath string) (string, error) {
name, err := base62.Random(16)
if err != nil {
return "", err
}
output := e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credential-libraries", "create", "vault-ldap",
"-credential-store-id", credentialStoreId,
"-vault-path", vaultPath,
"-name", fmt.Sprintf("e2e vault credential library %s", name),
"-description", "e2e",
"-format", "json",
),
)
if output.Err != nil {
return "", fmt.Errorf("%w: %s", output.Err, string(output.Stderr))
}
var createCredentialLibraryResult credentiallibraries.CredentialLibraryCreateResult
err = json.Unmarshal(output.Stdout, &createCredentialLibraryResult)
if err != nil {
return "", err
}
credentialLibraryId := createCredentialLibraryResult.Item.Id
t.Logf("Created Vault LDAP Credential Library: %s", credentialLibraryId)
return credentialLibraryId, nil
}
// CreateStaticCredentialPasswordDomainApi uses the API to create a new username-password-domain credential in the
// provided static credential store.
// Returns the id of the new credential
@@ -0,0 +1,368 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package base_with_vault_test
import (
"context"
"encoding/json"
"fmt"
"slices"
"strconv"
"strings"
"testing"
"github.com/hashicorp/boundary/api/scopes"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/hashicorp/boundary/testing/internal/e2e/vault"
"github.com/stretchr/testify/require"
)
// TestApiVaultLdapCredentialLibrary uses the Boundary API to test CRUDL on a
// Vault LDAP credential library. Additionally, it tests assigning it as a
// brokered credential source on a TCP target and connecting.
func TestApiVaultLdapCredentialLibrary(t *testing.T) {
e2e.MaybeSkipTest(t)
c, err := loadTestConfig()
require.NoError(t, err)
require.NotNil(t, c)
client, err := boundary.NewApiClient()
require.NoError(t, err)
require.NotNil(t, client)
orgId, err := boundary.CreateOrgApi(t, t.Context(), client)
require.NoError(t, err)
t.Cleanup(func() {
_, err := scopes.NewClient(client).Delete(context.Background(), orgId)
require.NoError(t, err)
})
projectId, err := boundary.CreateProjectApi(t, t.Context(), client, orgId)
require.NoError(t, err)
// Create TCP target.
targetPort, err := strconv.ParseUint(c.TargetPort, 10, 32)
require.NoError(t, err)
targetId, err := boundary.CreateTargetApi(t, t.Context(), client, projectId, "tcp",
targets.WithTcpTargetDefaultPort(uint32(targetPort)),
targets.WithTcpTargetDefaultClientPort(uint32(targetPort)),
)
require.NoError(t, err)
// Create host catalog, host set and host; Add host set to the target as a
// host source.
hostCatalogId, err := boundary.CreateHostCatalogApi(t, t.Context(), client, projectId)
require.NoError(t, err)
hostSetId, err := boundary.CreateHostSetApi(t, t.Context(), client, hostCatalogId)
require.NoError(t, err)
hostId, err := boundary.CreateHostApi(t, t.Context(), client, hostCatalogId, c.TargetAddress)
require.NoError(t, err)
err = boundary.AddHostToHostSetApi(t, t.Context(), client, hostSetId, hostId)
require.NoError(t, err)
err = boundary.AddHostSourceToTargetApi(t, t.Context(), client, targetId, hostSetId)
require.NoError(t, err)
// Configure Vault for LDAP.
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(context.Background(), "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
ldapPolicyName := vault.SetupLdap(t, c.VaultLdapPath,
c.LdapAddress, c.LdapAdminDn, c.LdapAdminPassword,
c.LdapDomainDn, c.LdapUserName, c.LdapGroupName,
)
t.Cleanup(func() {
// Destroy LDAP secrets engine and LDAP policy.
output := e2e.RunCommand(context.Background(), "vault",
e2e.WithArgs("secrets", "disable", c.VaultLdapPath),
)
require.NoError(t, output.Err, string(output.Stderr))
output = e2e.RunCommand(context.Background(), "vault",
e2e.WithArgs("policy", "delete", ldapPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
// Create Vault token for Boundary.
output := e2e.RunCommand(t.Context(), "vault",
e2e.WithArgs(
"token", "create",
"-no-default-policy=true",
fmt.Sprintf("-policy=%s", boundaryPolicyName),
fmt.Sprintf("-policy=%s", ldapPolicyName),
"-orphan=true",
"-period=20m",
"-renewable=true",
"-format=json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var tokenCreateResult vault.CreateTokenResponse
err = json.Unmarshal(output.Stdout, &tokenCreateResult)
require.NoError(t, err)
vaultToken := tokenCreateResult.Auth.Client_Token
// Create two Vault LDAP credential libraries: One with static, one with
// dynamic creds.
storeId, err := boundary.CreateCredentialStoreVaultApi(t, t.Context(), client, projectId, c.VaultAddr, vaultToken)
require.NoError(t, err)
staticCredVclId, err := boundary.CreateVaultLdapCredentialLibraryApi(t, t.Context(), client, storeId, fmt.Sprintf("%s/static-cred/%s", c.VaultLdapPath, c.LdapUserName))
require.NoError(t, err)
dynamicCredVclId, err := boundary.CreateVaultLdapCredentialLibraryApi(t, t.Context(), client, storeId, fmt.Sprintf("%s/creds/%s", c.VaultLdapPath, c.LdapGroupName))
require.NoError(t, err)
err = boundary.AddBrokeredCredentialSourceToTargetApi(t, t.Context(), client, targetId, staticCredVclId)
require.NoError(t, err)
err = boundary.AddBrokeredCredentialSourceToTargetApi(t, t.Context(), client, targetId, dynamicCredVclId)
require.NoError(t, err)
// Authorize session and assert that session credentials exist.
sar, err := targets.NewClient(client).AuthorizeSession(t.Context(), targetId)
require.NoError(t, err)
require.NotNil(t, sar)
sa, err := sar.GetSessionAuthorization()
require.NoError(t, err)
require.NotNil(t, sa)
require.Len(t, sa.Credentials, 2) // One for static, one for dynamic role.
slices.SortFunc(sa.Credentials, func(a *targets.SessionCredential, b *targets.SessionCredential) int {
// Sort by static role credential library first.
if strings.EqualFold(staticCredVclId, a.CredentialSource.Id) {
return -1
}
return 1
})
// Determine the LDAP server's domain using LDAP dn. LdapDomainDn is a
// string like "dc=subdomain,dc=domain,dc=tld".
expDomain := strings.ReplaceAll(
strings.ReplaceAll(c.LdapDomainDn, "dc=", ""), // Transforms into "subdomain,domain,tld".
",", ".", // Then replace commas with periods to get "subdomain.domain.tld".
)
// Static credential.
require.NotEmpty(t, sa.Credentials[0].Credential)
require.Contains(t, sa.Credentials[0].Credential, "username")
require.Contains(t, sa.Credentials[0].Credential, "password")
require.Contains(t, sa.Credentials[0].Credential, "domain")
require.EqualValues(t, c.LdapUserName, sa.Credentials[0].Credential["username"])
require.NotEmpty(t, sa.Credentials[0].Credential["password"])
require.EqualValues(t, expDomain, sa.Credentials[0].Credential["domain"])
require.NotNil(t, sa.Credentials[0].CredentialSource)
require.EqualValues(t, staticCredVclId, sa.Credentials[0].CredentialSource.Id)
require.Contains(t, sa.Credentials[0].CredentialSource.Name, "e2e vault credential library")
require.EqualValues(t, "vault-ldap", sa.Credentials[0].CredentialSource.Type)
require.EqualValues(t, storeId, sa.Credentials[0].CredentialSource.CredentialStoreId)
require.EqualValues(t, "username_password_domain", sa.Credentials[0].CredentialSource.CredentialType)
require.NotNil(t, sa.Credentials[0].Secret)
require.NotEmpty(t, sa.Credentials[0].Secret.Decoded)
require.Contains(t, sa.Credentials[0].Secret.Decoded, "username")
require.Contains(t, sa.Credentials[0].Secret.Decoded, "password")
require.Contains(t, sa.Credentials[0].Secret.Decoded, "dn")
require.EqualValues(t, c.LdapUserName, sa.Credentials[0].Secret.Decoded["username"])
require.EqualValues(t, sa.Credentials[0].Credential["password"], sa.Credentials[0].Secret.Decoded["password"])
require.EqualValues(t, fmt.Sprintf("cn=%s,%s", c.LdapUserName, c.LdapDomainDn), sa.Credentials[0].Secret.Decoded["dn"])
// Dynamic credential.
require.NotEmpty(t, sa.Credentials[1].Credential)
require.Contains(t, sa.Credentials[1].Credential, "username")
require.Contains(t, sa.Credentials[1].Credential, "password")
require.Contains(t, sa.Credentials[1].Credential, "domain")
require.Contains(t, sa.Credentials[1].Credential["username"], fmt.Sprintf("b_token_%s_", c.LdapGroupName))
require.NotEmpty(t, sa.Credentials[1].Credential["password"])
require.EqualValues(t, expDomain, sa.Credentials[1].Credential["domain"])
require.NotNil(t, sa.Credentials[1].CredentialSource)
require.EqualValues(t, dynamicCredVclId, sa.Credentials[1].CredentialSource.Id)
require.Contains(t, sa.Credentials[1].CredentialSource.Name, "e2e vault credential library")
require.EqualValues(t, "vault-ldap", sa.Credentials[1].CredentialSource.Type)
require.EqualValues(t, storeId, sa.Credentials[1].CredentialSource.CredentialStoreId)
require.EqualValues(t, "username_password_domain", sa.Credentials[1].CredentialSource.CredentialType)
require.NotNil(t, sa.Credentials[1].Secret)
require.NotEmpty(t, sa.Credentials[1].Secret.Decoded)
require.Contains(t, sa.Credentials[1].Secret.Decoded, "username")
require.Contains(t, sa.Credentials[1].Secret.Decoded, "password")
require.Contains(t, sa.Credentials[1].Secret.Decoded, "distinguished_names")
require.EqualValues(t, sa.Credentials[1].Credential["username"], sa.Credentials[1].Secret.Decoded["username"])
require.EqualValues(t, sa.Credentials[1].Credential["password"], sa.Credentials[1].Secret.Decoded["password"])
distinguishedNames, ok := sa.Credentials[1].Secret.Decoded["distinguished_names"].([]any)
require.True(t, ok)
require.NotEmpty(t, distinguishedNames)
require.Contains(t, distinguishedNames, fmt.Sprintf("cn=%s,%s", sa.Credentials[1].Secret.Decoded["username"], c.LdapDomainDn))
require.Contains(t, distinguishedNames, fmt.Sprintf("cn=%s,%s", c.LdapGroupName, c.LdapDomainDn))
}
// TestCliVaultLdapCredentialLibrary uses the Boundary CLI to test CRUDL on a
// Vault LDAP credential library. Additionally, it tests assigning it as a
// brokered credential source on a TCP target and connecting.
func TestCliVaultLdapCredentialLibrary(t *testing.T) {
e2e.MaybeSkipTest(t)
c, err := loadTestConfig()
require.NoError(t, err)
boundary.AuthenticateAdminCli(t, t.Context())
orgId, err := boundary.CreateOrgCli(t, t.Context())
require.NoError(t, err)
t.Cleanup(func() {
boundary.AuthenticateAdminCli(t, context.Background())
output := e2e.RunCommand(context.Background(), "boundary", e2e.WithArgs("scopes", "delete", "-id", orgId))
require.NoError(t, output.Err, string(output.Stderr))
})
projectId, err := boundary.CreateProjectCli(t, t.Context(), orgId)
require.NoError(t, err)
hostCatalogId, err := boundary.CreateHostCatalogCli(t, t.Context(), projectId)
require.NoError(t, err)
hostSetId, err := boundary.CreateHostSetCli(t, t.Context(), hostCatalogId)
require.NoError(t, err)
hostId, err := boundary.CreateHostCli(t, t.Context(), hostCatalogId, c.TargetAddress)
require.NoError(t, err)
err = boundary.AddHostToHostSetCli(t, t.Context(), hostSetId, hostId)
require.NoError(t, err)
targetId, err := boundary.CreateTargetCli(t, t.Context(), projectId, c.TargetPort)
require.NoError(t, err)
err = boundary.AddHostSourceToTargetCli(t, t.Context(), targetId, hostSetId)
require.NoError(t, err)
// Configure Vault for LDAP.
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(context.Background(), "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
ldapPolicyName := vault.SetupLdap(t, c.VaultLdapPath,
c.LdapAddress, c.LdapAdminDn, c.LdapAdminPassword,
c.LdapDomainDn, c.LdapUserName, c.LdapGroupName,
)
t.Cleanup(func() {
// Destroy LDAP secrets engine and LDAP policy.
output := e2e.RunCommand(context.Background(), "vault",
e2e.WithArgs("secrets", "disable", c.VaultLdapPath),
)
require.NoError(t, output.Err, string(output.Stderr))
output = e2e.RunCommand(context.Background(), "vault",
e2e.WithArgs("policy", "delete", ldapPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
// Create Vault token for Boundary.
output := e2e.RunCommand(t.Context(), "vault",
e2e.WithArgs(
"token", "create",
"-no-default-policy=true",
fmt.Sprintf("-policy=%s", boundaryPolicyName),
fmt.Sprintf("-policy=%s", ldapPolicyName),
"-orphan=true",
"-period=20m",
"-renewable=true",
"-format=json",
),
)
require.NoError(t, output.Err, string(output.Stderr))
var tokenCreateResult vault.CreateTokenResponse
err = json.Unmarshal(output.Stdout, &tokenCreateResult)
require.NoError(t, err)
vaultToken := tokenCreateResult.Auth.Client_Token
// Create two Vault LDAP credential libraries: One with static, one with
// dynamic creds.
storeId, err := boundary.CreateCredentialStoreVaultCli(t, t.Context(), projectId, c.VaultAddr, vaultToken)
require.NoError(t, err)
staticCredVclId, err := boundary.CreateVaultLdapCredentialLibraryCli(t, t.Context(), storeId, fmt.Sprintf("%s/static-cred/%s", c.VaultLdapPath, c.LdapUserName))
require.NoError(t, err)
dynamicCredVclId, err := boundary.CreateVaultLdapCredentialLibraryCli(t, t.Context(), storeId, fmt.Sprintf("%s/creds/%s", c.VaultLdapPath, c.LdapGroupName))
require.NoError(t, err)
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, t.Context(), targetId, staticCredVclId)
require.NoError(t, err)
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, t.Context(), targetId, dynamicCredVclId)
require.NoError(t, err)
// Connect and assert that session credentials exist.
out := boundary.ConnectCli(t, t.Context(), targetId)
require.NotEmpty(t, out)
require.Len(t, out.Credentials, 2) // One for static, one for dynamic role.
slices.SortFunc(out.Credentials, func(a *targets.SessionCredential, b *targets.SessionCredential) int {
// Sort by static role credential library first.
if strings.EqualFold(staticCredVclId, a.CredentialSource.Id) {
return -1
}
return 1
})
// Determine the LDAP server's domain using LDAP dn. LdapDomainDn is a
// string like "dc=subdomain,dc=domain,dc=tld".
expDomain := strings.ReplaceAll(
strings.ReplaceAll(c.LdapDomainDn, "dc=", ""), // Transforms into "subdomain,domain,tld".
",", ".", // Then replace commas with periods to get "subdomain.domain.tld".
)
// Static credential.
require.NotEmpty(t, out.Credentials[0].Credential)
require.Contains(t, out.Credentials[0].Credential, "username")
require.Contains(t, out.Credentials[0].Credential, "password")
require.Contains(t, out.Credentials[0].Credential, "domain")
require.EqualValues(t, c.LdapUserName, out.Credentials[0].Credential["username"])
require.NotEmpty(t, out.Credentials[0].Credential["password"])
require.EqualValues(t, expDomain, out.Credentials[0].Credential["domain"])
require.NotNil(t, out.Credentials[0].CredentialSource)
require.EqualValues(t, staticCredVclId, out.Credentials[0].CredentialSource.Id)
require.Contains(t, out.Credentials[0].CredentialSource.Name, "e2e vault credential library")
require.EqualValues(t, "vault-ldap", out.Credentials[0].CredentialSource.Type)
require.EqualValues(t, storeId, out.Credentials[0].CredentialSource.CredentialStoreId)
require.EqualValues(t, "username_password_domain", out.Credentials[0].CredentialSource.CredentialType)
require.NotNil(t, out.Credentials[0].Secret)
require.NotEmpty(t, out.Credentials[0].Secret.Decoded)
require.Contains(t, out.Credentials[0].Secret.Decoded, "username")
require.Contains(t, out.Credentials[0].Secret.Decoded, "password")
require.Contains(t, out.Credentials[0].Secret.Decoded, "dn")
require.EqualValues(t, c.LdapUserName, out.Credentials[0].Secret.Decoded["username"])
require.EqualValues(t, out.Credentials[0].Credential["password"], out.Credentials[0].Secret.Decoded["password"])
require.EqualValues(t, fmt.Sprintf("cn=%s,%s", c.LdapUserName, c.LdapDomainDn), out.Credentials[0].Secret.Decoded["dn"])
// Dynamic credential.
require.NotEmpty(t, out.Credentials[1].Credential)
require.Contains(t, out.Credentials[1].Credential, "username")
require.Contains(t, out.Credentials[1].Credential, "password")
require.Contains(t, out.Credentials[1].Credential, "domain")
require.Contains(t, out.Credentials[1].Credential["username"], fmt.Sprintf("b_token_%s_", c.LdapGroupName))
require.NotEmpty(t, out.Credentials[1].Credential["password"])
require.EqualValues(t, expDomain, out.Credentials[1].Credential["domain"])
require.NotNil(t, out.Credentials[1].CredentialSource)
require.EqualValues(t, dynamicCredVclId, out.Credentials[1].CredentialSource.Id)
require.Contains(t, out.Credentials[1].CredentialSource.Name, "e2e vault credential library")
require.EqualValues(t, "vault-ldap", out.Credentials[1].CredentialSource.Type)
require.EqualValues(t, storeId, out.Credentials[1].CredentialSource.CredentialStoreId)
require.EqualValues(t, "username_password_domain", out.Credentials[1].CredentialSource.CredentialType)
require.NotNil(t, out.Credentials[1].Secret)
require.NotEmpty(t, out.Credentials[1].Secret.Decoded)
require.Contains(t, out.Credentials[1].Secret.Decoded, "username")
require.Contains(t, out.Credentials[1].Secret.Decoded, "password")
require.Contains(t, out.Credentials[1].Secret.Decoded, "distinguished_names")
require.EqualValues(t, out.Credentials[1].Credential["username"], out.Credentials[1].Secret.Decoded["username"])
require.EqualValues(t, out.Credentials[1].Credential["password"], out.Credentials[1].Secret.Decoded["password"])
distinguishedNames, ok := out.Credentials[1].Secret.Decoded["distinguished_names"].([]any)
require.True(t, ok)
require.NotEmpty(t, distinguishedNames)
require.Contains(t, distinguishedNames, fmt.Sprintf("cn=%s,%s", out.Credentials[1].Secret.Decoded["username"], c.LdapDomainDn))
require.Contains(t, distinguishedNames, fmt.Sprintf("cn=%s,%s", c.LdapGroupName, c.LdapDomainDn))
}
@@ -55,7 +55,7 @@ func TestCliVaultCredentialStore(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
@@ -75,13 +75,26 @@ func TestCliVaultCredentialStore(t *testing.T) {
})
// Create credentials in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
passwordSecretName, password := vault.CreateKvPasswordCredential(t, c.VaultSecretPath, c.TargetSshUser, kvPolicyFilePath)
domainSecretName, domainPassword := vault.CreateKvPasswordDomainCredential(t, c.VaultSecretPath, c.TargetSshUser, "domain.com", kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", kvPolicyName),
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
passwordSecretName, passwordPolicyName, password := vault.CreateKvPasswordCredential(t, c.VaultSecretPath, c.TargetSshUser)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", passwordPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
domainSecretName, domainPolicyName, domainPassword := vault.CreateKvPasswordDomainCredential(t, c.VaultSecretPath, c.TargetSshUser, "domain.com")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", domainPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
@@ -93,7 +106,9 @@ func TestCliVaultCredentialStore(t *testing.T) {
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+kvPolicyName,
"-policy="+privateKeyPolicyName,
"-policy="+passwordPolicyName,
"-policy="+domainPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -243,7 +258,7 @@ func TestApiVaultCredentialStore(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("secrets", "enable", "-path="+c.VaultSecretPath, "kv-v2"),
)
@@ -255,11 +270,30 @@ func TestApiVaultCredentialStore(t *testing.T) {
require.NoError(t, output.Err, string(output.Stderr))
})
// Create credential in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
passwordSecretName, password := vault.CreateKvPasswordCredential(t, c.VaultSecretPath, c.TargetSshUser, kvPolicyFilePath)
domainSecretName, domainPassword := vault.CreateKvPasswordDomainCredential(t, c.VaultSecretPath, c.TargetSshUser, "domain.com", kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
// Create credentials in vault
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
passwordSecretName, passwordPolicyName, password := vault.CreateKvPasswordCredential(t, c.VaultSecretPath, c.TargetSshUser)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", passwordPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
domainSecretName, domainPolicyName, domainPassword := vault.CreateKvPasswordDomainCredential(t, c.VaultSecretPath, c.TargetSshUser, "domain.com")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", domainPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
t.Log("Created Vault Credentials")
// Create vault token for boundary
@@ -268,7 +302,9 @@ func TestApiVaultCredentialStore(t *testing.T) {
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+kvPolicyName,
"-policy="+privateKeyPolicyName,
"-policy="+passwordPolicyName,
"-policy="+domainPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -16,7 +16,16 @@ type config struct {
VaultAddr string `envconfig:"E2E_VAULT_ADDR_PUBLIC" required:"true"` // e.g. "http://127.0.0.1:8200"
VaultAddrPrivate string `envconfig:"E2E_VAULT_ADDR_PRIVATE" required:"true"` // e.g. "http://10.10.10.10:8200"
VaultSecretPath string `envconfig:"E2E_VAULT_SECRET_PATH" default:"e2e_secrets"`
VaultLdapPath string `envconfig:"E2E_VAULT_LDAP_PATH" default:"e2e_ldap"`
MaxPageSize int `envconfig:"E2E_MAX_PAGE_SIZE" default:"1000"`
// Ldap configuration.
LdapAddress string `envconfig:"E2E_LDAP_ADDR" required:"true"` // e.g. ldap://ldap
LdapDomainDn string `envconfig:"E2E_LDAP_DOMAIN_DN" required:"true"` // e.g. dc=example,dc=org
LdapAdminDn string `envconfig:"E2E_LDAP_ADMIN_DN" required:"true"` // e.g. cn=admin,dc=example,dc=org
LdapAdminPassword string `envconfig:"E2E_LDAP_ADMIN_PASSWORD" required:"true"`
LdapUserName string `envconfig:"E2E_LDAP_USER_NAME" required:"true"`
LdapUserPassword string `envconfig:"E2E_LDAP_USER_PASSWORD" required:"true"`
LdapGroupName string `envconfig:"E2E_LDAP_GROUP_NAME" required:"true"`
}
func loadTestConfig() (*config, error) {
@@ -41,7 +41,7 @@ func TestCliPaginateCredentialLibraries(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
@@ -61,11 +61,10 @@ func TestCliPaginateCredentialLibraries(t *testing.T) {
})
// Create credentials in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", kvPolicyName),
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
@@ -76,7 +75,7 @@ func TestCliPaginateCredentialLibraries(t *testing.T) {
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+kvPolicyName,
"-policy="+privateKeyPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -194,7 +193,7 @@ func TestApiPaginateCredentialLibraries(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
@@ -214,11 +213,10 @@ func TestApiPaginateCredentialLibraries(t *testing.T) {
})
// Create credentials in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", kvPolicyName),
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
@@ -229,7 +227,7 @@ func TestApiPaginateCredentialLibraries(t *testing.T) {
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+kvPolicyName,
"-policy="+privateKeyPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -53,7 +53,7 @@ func TestCliTcpTargetVaultGenericConnectTargetWithAuthzToken(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
@@ -73,11 +73,10 @@ func TestCliTcpTargetVaultGenericConnectTargetWithAuthzToken(t *testing.T) {
})
// Create credential in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", kvPolicyName),
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
@@ -89,7 +88,7 @@ func TestCliTcpTargetVaultGenericConnectTargetWithAuthzToken(t *testing.T) {
"token", "create",
"-no-default-policy=true",
fmt.Sprintf("-policy=%s", boundaryPolicyName),
fmt.Sprintf("-policy=%s", kvPolicyName),
fmt.Sprintf("-policy=%s", privateKeyPolicyName),
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -53,7 +53,7 @@ func TestCliTcpTargetVaultGenericConnectTargetWithSsh(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
@@ -73,11 +73,10 @@ func TestCliTcpTargetVaultGenericConnectTargetWithSsh(t *testing.T) {
})
// Create credential in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", kvPolicyName),
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
@@ -89,7 +88,7 @@ func TestCliTcpTargetVaultGenericConnectTargetWithSsh(t *testing.T) {
"token", "create",
"-no-default-policy=true",
fmt.Sprintf("-policy=%s", boundaryPolicyName),
fmt.Sprintf("-policy=%s", kvPolicyName),
fmt.Sprintf("-policy=%s", privateKeyPolicyName),
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -54,7 +54,7 @@ func TestCliTcpTargetVaultGenericConnectTarget(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
@@ -74,11 +74,10 @@ func TestCliTcpTargetVaultGenericConnectTarget(t *testing.T) {
})
// Create credential in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", kvPolicyName),
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
@@ -90,7 +89,7 @@ func TestCliTcpTargetVaultGenericConnectTarget(t *testing.T) {
"token", "create",
"-no-default-policy=true",
fmt.Sprintf("-policy=%s", boundaryPolicyName),
fmt.Sprintf("-policy=%s", kvPolicyName),
fmt.Sprintf("-policy=%s", privateKeyPolicyName),
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -49,7 +49,7 @@ func TestCliTcpTargetWorkerConnectTarget(t *testing.T) {
require.NoError(t, err)
// Configure vault
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", boundaryPolicyName),
@@ -69,11 +69,10 @@ func TestCliTcpTargetWorkerConnectTarget(t *testing.T) {
})
// Create credential in vault
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", kvPolicyName),
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
@@ -85,7 +84,7 @@ func TestCliTcpTargetWorkerConnectTarget(t *testing.T) {
"token", "create",
"-no-default-policy=true",
fmt.Sprintf("-policy=%s", boundaryPolicyName),
fmt.Sprintf("-policy=%s", kvPolicyName),
fmt.Sprintf("-policy=%s", privateKeyPolicyName),
"-orphan=true",
"-period=20m",
"-renewable=true",
@@ -278,22 +278,36 @@ func populateBoundaryDatabase(t testing.TB, ctx context.Context, c *config, te T
require.NoError(t, err)
// Create vault credentials
boundaryPolicyName, kvPolicyFilePath := vault.Setup(t, "testdata/boundary-controller-policy.hcl")
boundaryPolicyName := vault.SetupForBoundaryController(t, "testdata/boundary-controller-policy.hcl")
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("secrets", "enable", "-path="+c.VaultSecretPath, "kv-v2"),
)
require.NoError(t, output.Err, string(output.Stderr))
privateKeySecretName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath, kvPolicyFilePath)
passwordSecretName, _ := vault.CreateKvPasswordCredential(t, c.VaultSecretPath, c.TargetSshUser, kvPolicyFilePath)
kvPolicyName := vault.WritePolicy(t, ctx, kvPolicyFilePath)
privateKeySecretName, privateKeyPolicyName := vault.CreateKvPrivateKeyCredential(t, c.VaultSecretPath, c.TargetSshUser, c.TargetSshKeyPath)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", privateKeyPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
passwordSecretName, passwordPolicyName, _ := vault.CreateKvPasswordCredential(t, c.VaultSecretPath, c.TargetSshUser)
t.Cleanup(func() {
output := e2e.RunCommand(ctx, "vault",
e2e.WithArgs("policy", "delete", passwordPolicyName),
)
require.NoError(t, output.Err, string(output.Stderr))
})
t.Log("Created Vault Credential")
output = e2e.RunCommand(ctx, "vault",
e2e.WithArgs(
"token", "create",
"-no-default-policy=true",
"-policy="+boundaryPolicyName,
"-policy="+kvPolicyName,
"-policy="+privateKeyPolicyName,
"-policy="+passwordPolicyName,
"-orphan=true",
"-period=20m",
"-renewable=true",
+155 -34
View File
@@ -8,7 +8,9 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/boundary/testing/internal/e2e"
@@ -23,37 +25,143 @@ type CreateTokenResponse struct {
}
}
// Setup verifies if appropriate credentials are set and adds the boundary controller
// policy to vault. Returns the vault address.
func Setup(t testing.TB, boundaryControllerFilePath string) (boundaryPolicyName string, kvPolicyFilePath string) {
// SetupForBoundaryController verifies if appropriate credentials are set and
// adds the boundary controller policy to vault. Returns the policy name.
func SetupForBoundaryController(t testing.TB, boundaryControllerFilePath string) (boundaryPolicyName string) {
// Set up boundary policy
boundaryPolicyFilePath, err := filepath.Abs(boundaryControllerFilePath)
require.NoError(t, err)
boundaryPolicyName = WritePolicy(t, context.Background(), boundaryPolicyFilePath)
boundaryPolicyName = WritePolicy(t, t.Context(), boundaryPolicyFilePath)
// Create kv policy
kvPolicyFilePath = fmt.Sprintf("%s/%s", t.TempDir(), "kv-policy.hcl")
_, err = os.Create(kvPolicyFilePath)
require.NoError(t, err)
return boundaryPolicyName, kvPolicyFilePath
return boundaryPolicyName
}
// CreateKvPrivateKeyCredential creates a private key credential in vault and creates a vault policy
// to be able to read that credential. Returns the name of the policy.
func CreateKvPrivateKeyCredential(t testing.TB, secretPath string, user string, keyPath string, kvPolicyFilePath string) string {
// SetupLdap sets a Vault server up for LDAP against an OpenLDAP server. It
// enables the LDAP secrets engine, configures it and creates a static user
// according to what is in OpenLDAP. Additionally, it sets up Vault's ability to
// manage LDAP users dynamically. Note that this function does not put any
// clean-up in place to run after a test is complete. When applicable, callers
// should destroy the Vault LDAP policy and LDAP secrets engine this function
// creates.
func SetupLdap(t testing.TB, vaultLdapMountPath, ldapAddr, ldapAdminDn, ldapAdminPw, ldapDn, ldapUser, ldapGroup string) string {
// Enable LDAP secrets engine.
output := e2e.RunCommand(t.Context(), "vault",
e2e.WithArgs("secrets", "enable", fmt.Sprintf("-path=%s", vaultLdapMountPath), "ldap"),
)
require.NoError(t, output.Err, string(output.Stderr))
// Define and write LDAP access policy to Vault.
vaultLdapPolicyFilePath := path.Join(t.TempDir(), "ldap-policy.hcl")
f, err := os.Create(vaultLdapPolicyFilePath)
require.NoError(t, err)
_, err = fmt.Fprintf(f, `
path "%[1]s/static-cred/%[2]s" {
capabilities = ["read"]
}
path "%[1]s/static-role/%[2]s" {
capabilities = ["create", "read", "update", "patch", "delete", "list"]
}
path "%[1]s/creds/%[3]s" {
capabilities = ["read"]
}
path "%[1]s/role/%[3]s" {
capabilities = ["create", "read", "update", "patch", "delete", "list"]
}
`, vaultLdapMountPath, ldapUser, ldapGroup)
require.NoError(t, err)
require.NoError(t, f.Sync())
_ = f.Close()
policyName := WritePolicy(t, t.Context(), vaultLdapPolicyFilePath)
// Configure LDAP secrets engine to point to existing OpenLDAP server.
output = e2e.RunCommand(t.Context(), "vault",
e2e.WithArgs(
"write",
fmt.Sprintf("%s/config", vaultLdapMountPath),
fmt.Sprintf("url=%s", ldapAddr),
fmt.Sprintf("binddn=%s", ldapAdminDn),
fmt.Sprintf("bindpass=%s", ldapAdminPw),
),
)
require.NoError(t, output.Err, string(output.Stderr))
// Create static LDAP user in Vault (already defined in OpenLDAP server).
output = e2e.RunCommand(t.Context(), "vault",
e2e.WithArgs(
"write",
fmt.Sprintf("%s/static-role/%s", vaultLdapMountPath, ldapUser),
fmt.Sprintf("dn=cn=%s,%s", ldapUser, ldapDn),
fmt.Sprintf("username=%s", ldapUser),
"rotation_period=24h",
),
)
require.NoError(t, output.Err, string(output.Stderr))
// Create Vault dynamic role for LDAP group.
createLdif := fmt.Sprintf(`
dn: cn={{.Username}},%[1]s
changetype: add
objectClass: inetOrgPerson
cn: {{.Username}}
sn: {{.Username}}
uid: {{.Username}}
userPassword:{{.Password}}
dn: cn=%s,%[1]s
changetype: modify
add: uniqueMember
uniqueMember: cn={{.Username}},%[1]s
-
`, ldapDn, ldapGroup)
createLdif = strings.ReplaceAll(createLdif, "\t", "")
deleteLdif := fmt.Sprintf(`
dn: cn={{.Username}},%s
changetype: delete
`, ldapDn)
deleteLdif = strings.ReplaceAll(deleteLdif, "\t", "")
output = e2e.RunCommand(t.Context(), "vault",
e2e.WithArgs(
"write",
fmt.Sprintf("%s/role/%s", vaultLdapMountPath, ldapGroup),
"username_template=b_{{.DisplayName}}_{{.RoleName}}_{{random 10}}_{{unix_time}}",
fmt.Sprintf("creation_ldif=%s", createLdif),
fmt.Sprintf("rollback_ldif=%s", deleteLdif),
fmt.Sprintf("deletion_ldif=%s", deleteLdif),
"default_ttl=1h",
"max_ttxl=24h",
),
)
require.NoError(t, output.Err, string(output.Stderr))
return policyName
}
// CreateKvPrivateKeyCredential creates a private key credential in vault and
// creates a vault policy to be able to read that credential. Returns the secret
// and policy names. Note that this function does not put any clean-up in place
// to run after a test is complete. When applicable, callers should destroy the
// policy and secret this function creates.
func CreateKvPrivateKeyCredential(t testing.TB, secretPath string, user string, keyPath string) (secretName string, policyName string) {
secretName, err := base62.Random(16)
require.NoError(t, err)
// Update policy file
f, err := os.OpenFile(kvPolicyFilePath, os.O_APPEND|os.O_WRONLY, 0o644)
policyFilePath := path.Join(t.TempDir(), fmt.Sprintf("kv-pk-%s-policy.hcl", secretName))
f, err := os.Create(policyFilePath)
require.NoError(t, err)
_, err = f.WriteString(fmt.Sprintf("path \"%s/data/%s\" { capabilities = [\"read\"] }\n",
_, err = fmt.Fprintf(f, "path \"%s/data/%s\" { capabilities = [\"read\"] }\n",
secretPath,
secretName,
))
)
require.NoError(t, err)
policyName = WritePolicy(t, t.Context(), policyFilePath)
// Create secret
output := e2e.RunCommand(context.Background(), "vault",
e2e.WithArgs(
@@ -66,24 +174,31 @@ func CreateKvPrivateKeyCredential(t testing.TB, secretPath string, user string,
)
require.NoError(t, output.Err, string(output.Stderr))
return secretName
return secretName, policyName
}
// CreateKvPasswordCredential creates a username/password credential in vault and creates a vault
// policy to be able to read that credential. Returns the name of the policy
func CreateKvPasswordCredential(t testing.TB, secretPath string, user string, kvPolicyFilePath string) (secretName string, password string) {
// CreateKvPasswordCredential creates a username/password credential in vault
// and creates a vault policy to be able to read that credential. Returns the
// secret and policy names as well as the password for the secret. Note that
// this function does not put any clean-up in place to run after a test is
// complete. When applicable, callers should destroy the policy and secret this
// function creates.
func CreateKvPasswordCredential(t testing.TB, secretPath string, user string) (secretName string, policyName string, password string) {
secretName, err := base62.Random(16)
require.NoError(t, err)
// Update policy file
f, err := os.OpenFile(kvPolicyFilePath, os.O_APPEND|os.O_WRONLY, 0o644)
policyFilePath := path.Join(t.TempDir(), fmt.Sprintf("kv-up-%s-policy.hcl", secretName))
f, err := os.Create(policyFilePath)
require.NoError(t, err)
_, err = f.WriteString(fmt.Sprintf("path \"%s/data/%s\" { capabilities = [\"read\"] }\n",
_, err = fmt.Fprintf(f, "path \"%s/data/%s\" { capabilities = [\"read\"] }\n",
secretPath,
secretName,
))
)
require.NoError(t, err)
policyName = WritePolicy(t, t.Context(), policyFilePath)
// Create secret
password, err = base62.Random(16)
require.NoError(t, err)
@@ -98,24 +213,30 @@ func CreateKvPasswordCredential(t testing.TB, secretPath string, user string, kv
)
require.NoError(t, output.Err, string(output.Stderr))
return secretName, password
return secretName, policyName, password
}
// CreateKvPasswordDomainCredential creates a username/password/domain credential in vault and creates a vault
// policy to be able to read that credential. Returns the name of the policy
func CreateKvPasswordDomainCredential(t testing.TB, secretPath string, user string, domain string, kvPolicyFilePath string) (secretName string, password string) {
// CreateKvPasswordDomainCredential creates a username/password/domain
// credential in vault and creates a vault policy to be able to read that
// credential. Returns the name of the policy. Note that this function does not
// put any clean-up in place to run after a test is complete. When applicable,
// callers should destroy the policy and secret this function creates.
func CreateKvPasswordDomainCredential(t testing.TB, secretPath string, user string, domain string) (secretName string, policyName string, password string) {
secretName, err := base62.Random(16)
require.NoError(t, err)
// Update policy file
f, err := os.OpenFile(kvPolicyFilePath, os.O_APPEND|os.O_WRONLY, 0o644)
policyFilePath := path.Join(t.TempDir(), fmt.Sprintf("kv-upd-%s-policy.hcl", secretName))
f, err := os.Create(policyFilePath)
require.NoError(t, err)
_, err = f.WriteString(fmt.Sprintf("path \"%s/data/%s\" { capabilities = [\"read\"] }\n",
_, err = fmt.Fprintf(f, "path \"%s/data/%s\" { capabilities = [\"read\"] }\n",
secretPath,
secretName,
))
)
require.NoError(t, err)
policyName = WritePolicy(t, t.Context(), policyFilePath)
// Create secret
password, err = base62.Random(16)
require.NoError(t, err)
@@ -131,7 +252,7 @@ func CreateKvPasswordDomainCredential(t testing.TB, secretPath string, user stri
)
require.NoError(t, output.Err, string(output.Stderr))
return secretName, password
return secretName, policyName, password
}
// WritePolicy adds a policy to vault. Provide a name for the policy that you want to create as well