diff --git a/.github/workflows/schema-diff.yml b/.github/workflows/schema-diff.yml
index 750746afe..a3952af06 100644
--- a/.github/workflows/schema-diff.yml
+++ b/.github/workflows/schema-diff.yml
@@ -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)
+
+ Functions
- #### Tables
- $(getdiff .schema-diff/tables.diff)
+ $(getdiff .schema-diff/funcs.diff)
+
- #### Views
- $(getdiff .schema-diff/views.diff)
+
+ Tables
- #### Triggers
- $(getdiff .schema-diff/triggers.diff)
+ $(getdiff .schema-diff/tables.diff)
+
- #### Indexes
- $(getdiff .schema-diff/indexes.diff)
+
+ Views
- #### Constraints
- $(getdiff .schema-diff/constraints.diff)
+ $(getdiff .schema-diff/views.diff)
+
- #### Foreign Key Constraints
- $(getdiff .schema-diff/fk_constraints.diff)
+
+ Triggers
+
+ $(getdiff .schema-diff/triggers.diff)
+
+
+
+ Indexes
+
+ $(getdiff .schema-diff/indexes.diff)
+
+
+
+ Constraints
+
+ $(getdiff .schema-diff/constraints.diff)
+
+
+
+ Foreign Key Constraints
+
+ $(getdiff .schema-diff/fk_constraints.diff)
+
EOF
# Parse it through jq to build a valid json object.
diff --git a/api/credentiallibraries/option.gen.go b/api/credentiallibraries/option.gen.go
index ffbadffd7..9e9ff2584 100644
--- a/api/credentiallibraries/option.gen.go
+++ b/api/credentiallibraries/option.gen.go
@@ -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"]
diff --git a/api/credentiallibraries/vault_ldap_credential_library_attributes.gen.go b/api/credentiallibraries/vault_ldap_credential_library_attributes.gen.go
new file mode 100644
index 000000000..ce3b26dc2
--- /dev/null
+++ b/api/credentiallibraries/vault_ldap_credential_library_attributes.gen.go
@@ -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)
+}
diff --git a/enos/enos-scenario-e2e-docker-base-with-vault.hcl b/enos/enos-scenario-e2e-docker-base-with-vault.hcl
index 6c800c847..5e4b75f9a 100644
--- a/enos/enos-scenario-e2e-docker-base-with-vault.hcl
+++ b/enos/enos-scenario-e2e-docker-base-with-vault.hcl
@@ -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
}
}
}
diff --git a/globals/prefixes.go b/globals/prefixes.go
index 5286d664f..801b75f5a 100644
--- a/globals/prefixes.go
+++ b/globals/prefixes.go
@@ -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,
diff --git a/go.mod b/go.mod
index 5049649dd..a7a066c06 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 3590c2060..085cad4d0 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go
index 5b32f31dd..f204c9af1 100644
--- a/internal/api/genapi/input.go
+++ b/internal/api/genapi/input.go
@@ -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",
diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go
index f2f5f050c..bbd7cc37c 100644
--- a/internal/cmd/commands.go
+++ b/internal/cmd/commands.go
@@ -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{
diff --git a/internal/cmd/commands/credentiallibrariescmd/funcs.go b/internal/cmd/commands/credentiallibrariescmd/funcs.go
index c68d50e2f..287ecf4c1 100644
--- a/internal/cmd/commands/credentiallibrariescmd/funcs.go
+++ b/internal/cmd/commands/credentiallibrariescmd/funcs.go
@@ -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",
+}
diff --git a/internal/cmd/commands/credentiallibrariescmd/vault-ldap_credentiallibraries.gen.go b/internal/cmd/commands/credentiallibrariescmd/vault-ldap_credentiallibraries.gen.go
new file mode 100644
index 000000000..e2391ae24
--- /dev/null
+++ b/internal/cmd/commands/credentiallibrariescmd/vault-ldap_credentiallibraries.gen.go
@@ -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 }
+)
diff --git a/internal/cmd/commands/credentiallibrariescmd/vault-ldap_funcs.go b/internal/cmd/commands/credentiallibrariescmd/vault-ldap_funcs.go
new file mode 100644
index 000000000..be370e26d
--- /dev/null
+++ b/internal/cmd/commands/credentiallibrariescmd/vault-ldap_funcs.go
@@ -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()
+}
diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go
index b6174b96c..b5c1b68a0 100644
--- a/internal/cmd/gencli/input.go
+++ b/internal/cmd/gencli/input.go
@@ -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": {
{
diff --git a/internal/credential/service_list_libraries_ext_test.go b/internal/credential/service_list_libraries_ext_test.go
index 82dfb2f22..034dcd9df 100644
--- a/internal/credential/service_list_libraries_ext_test.go
+++ b/internal/credential/service_list_libraries_ext_test.go
@@ -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)
})
diff --git a/internal/credential/vault/credential_library.go b/internal/credential/vault/credential_library.go
index 8345dd1de..0294f1ceb 100644
--- a/internal/credential/vault/credential_library.go
+++ b/internal/credential/vault/credential_library.go
@@ -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"
}
diff --git a/internal/credential/vault/docker.go b/internal/credential/vault/docker.go
index fe8686291..7d7f9a108 100644
--- a/internal/credential/vault/docker.go
+++ b/internal/credential/vault/docker.go
@@ -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
+}
diff --git a/internal/credential/vault/ldap_credential_library.go b/internal/credential/vault/ldap_credential_library.go
new file mode 100644
index 000000000..3e2251b3d
--- /dev/null
+++ b/internal/credential/vault/ldap_credential_library.go
@@ -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"
+}
diff --git a/internal/credential/vault/mapping_overriders.go b/internal/credential/vault/mapping_overriders.go
index 089754102..eef7559e9 100644
--- a/internal/credential/vault/mapping_overriders.go
+++ b/internal/credential/vault/mapping_overriders.go
@@ -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.
diff --git a/internal/credential/vault/private_library.go b/internal/credential/vault/private_library.go
index 7e1288c7e..5a00b1a48 100644
--- a/internal/credential/vault/private_library.go
+++ b/internal/credential/vault/private_library.go
@@ -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
+}
diff --git a/internal/credential/vault/private_library_test.go b/internal/credential/vault/private_library_test.go
index b151df753..a57d4e2ab 100644
--- a/internal/credential/vault/private_library_test.go
+++ b/internal/credential/vault/private_library_test.go
@@ -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)
+ })
+ }
+}
diff --git a/internal/credential/vault/public_ids.go b/internal/credential/vault/public_ids.go
index cdea3f7ae..1a4b7f12d 100644
--- a/internal/credential/vault/public_ids.go
+++ b/internal/credential/vault/public_ids.go
@@ -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
+}
diff --git a/internal/credential/vault/query.go b/internal/credential/vault/query.go
index b65a1c40c..9fd09eef4 100644
--- a/internal/credential/vault/query.go
+++ b/internal/credential/vault/query.go
@@ -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
diff --git a/internal/credential/vault/repository_credential_library.go b/internal/credential/vault/repository_credential_library.go
index 9227c5b13..707d64ccf 100644
--- a/internal/credential/vault/repository_credential_library.go
+++ b/internal/credential/vault/repository_credential_library.go
@@ -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 }
diff --git a/internal/credential/vault/repository_ldap_credential_library.go b/internal/credential/vault/repository_ldap_credential_library.go
new file mode 100644
index 000000000..e3dd80622
--- /dev/null
+++ b/internal/credential/vault/repository_ldap_credential_library.go
@@ -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
+}
diff --git a/internal/credential/vault/repository_ldap_credential_library_test.go b/internal/credential/vault/repository_ldap_credential_library_test.go
new file mode 100644
index 000000000..56b45168f
--- /dev/null
+++ b/internal/credential/vault/repository_ldap_credential_library_test.go
@@ -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)
+ })
+}
diff --git a/internal/credential/vault/store/vault.pb.go b/internal/credential/vault/store/vault.pb.go
index 7f22e504c..637fa4fa0 100644
--- a/internal/credential/vault/store/vault.pb.go
+++ b/internal/credential/vault/store/vault.pb.go
@@ -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,
},
diff --git a/internal/credential/vault/supported.go b/internal/credential/vault/supported.go
index b65ad6317..fcc8e8fd9 100644
--- a/internal/credential/vault/supported.go
+++ b/internal/credential/vault/supported.go
@@ -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")
diff --git a/internal/credential/vault/testing.go b/internal/credential/vault/testing.go
index cb27228a8..e4bff5128 100644
--- a/internal/credential/vault/testing.go
+++ b/internal/credential/vault/testing.go
@@ -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 "/*" {
+// 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...)
+}
diff --git a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go
index 3c785c55e..b6a61c335 100644
--- a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go
+++ b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go
@@ -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 {
diff --git a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go
index 295235acd..e4b59200d 100644
--- a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go
+++ b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go
@@ -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
diff --git a/internal/daemon/controller/handlers/targets/target_service.go b/internal/daemon/controller/handlers/targets/target_service.go
index f7ae11100..8554c494c 100644
--- a/internal/daemon/controller/handlers/targets/target_service.go
+++ b/internal/daemon/controller/handlers/targets/target_service.go
@@ -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,
diff --git a/internal/db/schema/migrations/oss/postgres/10/04_vault_credential.up.sql b/internal/db/schema/migrations/oss/postgres/10/04_vault_credential.up.sql
index 2d109df2e..7304a5793 100644
--- a/internal/db/schema/migrations/oss/postgres/10/04_vault_credential.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/10/04_vault_credential.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/100/01_credential_vault_ldap_library.up.sql b/internal/db/schema/migrations/oss/postgres/100/01_credential_vault_ldap_library.up.sql
new file mode 100644
index 000000000..98967e865
--- /dev/null
+++ b/internal/db/schema/migrations/oss/postgres/100/01_credential_vault_ldap_library.up.sql
@@ -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;
diff --git a/internal/db/schema/migrations/oss/postgres/22/03_vault_library_map.up.sql b/internal/db/schema/migrations/oss/postgres/22/03_vault_library_map.up.sql
index 8d1877a99..e4e1f0d8c 100644
--- a/internal/db/schema/migrations/oss/postgres/22/03_vault_library_map.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/22/03_vault_library_map.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/22/04_vault_library_map_user_password.up.sql b/internal/db/schema/migrations/oss/postgres/22/04_vault_library_map_user_password.up.sql
index 203b2b13c..e63228749 100644
--- a/internal/db/schema/migrations/oss/postgres/22/04_vault_library_map_user_password.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/22/04_vault_library_map_user_password.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/36/01_vault_library_map_username_password.up.sql b/internal/db/schema/migrations/oss/postgres/36/01_vault_library_map_username_password.up.sql
index 52ea50f95..b3dfe0145 100644
--- a/internal/db/schema/migrations/oss/postgres/36/01_vault_library_map_username_password.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/36/01_vault_library_map_username_password.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/39/02_vault_ssh_private_key_override.up.sql b/internal/db/schema/migrations/oss/postgres/39/02_vault_ssh_private_key_override.up.sql
index 268ad9e81..0be0f10c2 100644
--- a/internal/db/schema/migrations/oss/postgres/39/02_vault_ssh_private_key_override.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/39/02_vault_ssh_private_key_override.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/63/01_credential_vault_ssh_cert_library.up.sql b/internal/db/schema/migrations/oss/postgres/63/01_credential_vault_ssh_cert_library.up.sql
index 0b80f5050..5d7393916 100644
--- a/internal/db/schema/migrations/oss/postgres/63/01_credential_vault_ssh_cert_library.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/63/01_credential_vault_ssh_cert_library.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql b/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql
index 564bd1847..c70acf944 100644
--- a/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/71/15_dynamic_credential_history.up.sql b/internal/db/schema/migrations/oss/postgres/71/15_dynamic_credential_history.up.sql
index 820ece1ff..9fb8ffb0e 100644
--- a/internal/db/schema/migrations/oss/postgres/71/15_dynamic_credential_history.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/71/15_dynamic_credential_history.up.sql
@@ -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,
diff --git a/internal/db/schema/migrations/oss/postgres/71/16_recording_dynamic_credential.up.sql b/internal/db/schema/migrations/oss/postgres/71/16_recording_dynamic_credential.up.sql
index c9c614e32..1f1828fee 100644
--- a/internal/db/schema/migrations/oss/postgres/71/16_recording_dynamic_credential.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/71/16_recording_dynamic_credential.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/71/17_credential_history_views.up.sql b/internal/db/schema/migrations/oss/postgres/71/17_credential_history_views.up.sql
index a5fcd38ab..01f02fedd 100644
--- a/internal/db/schema/migrations/oss/postgres/71/17_credential_history_views.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/71/17_credential_history_views.up.sql
@@ -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,
diff --git a/internal/db/schema/migrations/oss/postgres/81/01_deleted_tables_and_triggers.up.sql b/internal/db/schema/migrations/oss/postgres/81/01_deleted_tables_and_triggers.up.sql
index 1d65d241d..543f99a92 100644
--- a/internal/db/schema/migrations/oss/postgres/81/01_deleted_tables_and_triggers.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/81/01_deleted_tables_and_triggers.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/98/02_username_password_domain_vault.up.sql b/internal/db/schema/migrations/oss/postgres/98/02_username_password_domain_vault.up.sql
index 91720c6d2..429a3cd0d 100644
--- a/internal/db/schema/migrations/oss/postgres/98/02_username_password_domain_vault.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/98/02_username_password_domain_vault.up.sql
@@ -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
diff --git a/internal/db/schema/migrations/oss/postgres/98/04_rdp_targets.up.sql b/internal/db/schema/migrations/oss/postgres/98/04_rdp_targets.up.sql
index e16c47e6c..269f4cb77 100644
--- a/internal/db/schema/migrations/oss/postgres/98/04_rdp_targets.up.sql
+++ b/internal/db/schema/migrations/oss/postgres/98/04_rdp_targets.up.sql
@@ -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,
diff --git a/internal/db/schema/migrations/oss/postgres/99/01_credential_vault_library_refactor.up.sql b/internal/db/schema/migrations/oss/postgres/99/01_credential_vault_library_refactor.up.sql
new file mode 100644
index 000000000..21077d271
--- /dev/null
+++ b/internal/db/schema/migrations/oss/postgres/99/01_credential_vault_library_refactor.up.sql
@@ -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;
diff --git a/internal/db/schema/migrations/oss/postgres/99/README.md b/internal/db/schema/migrations/oss/postgres/99/README.md
new file mode 100644
index 000000000..6873d04d3
--- /dev/null
+++ b/internal/db/schema/migrations/oss/postgres/99/README.md
@@ -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.
diff --git a/internal/db/schema/migrations/oss/postgres_99_01_test.go b/internal/db/schema/migrations/oss/postgres_99_01_test.go
new file mode 100644
index 000000000..e3e80d22d
--- /dev/null
+++ b/internal/db/schema/migrations/oss/postgres_99_01_test.go
@@ -0,0 +1,1131 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: BUSL-1.1
+
+package oss_test
+
+import (
+ "database/sql"
+ "fmt"
+ "math/rand/v2"
+ "strconv"
+ "testing"
+ "time"
+
+ "github.com/hashicorp/boundary/internal/db/common"
+ "github.com/hashicorp/boundary/internal/db/schema"
+ "github.com/hashicorp/boundary/testing/dbtest"
+ "github.com/hashicorp/go-secure-stdlib/base62"
+ "github.com/stretchr/testify/require"
+)
+
+// TestVaultCredLibRefactor tests the credential_vault_library refactor. It
+// inserts state in the changed tables prior to the refactor migration, executes
+// the refactor migration and then asserts that this state is still correct.
+//
+// For structural tests, see
+// sqltest/tests/credential/vault/credential_vault_library_refactor.sql.
+//
+// For context, see 99/README.md.
+func TestVaultCredLibRefactor(t *testing.T) {
+ t.Parallel()
+ require := require.New(t)
+
+ c := testVaultCredLibRefactorDbSetupCounts{
+ orgs: 2,
+ projectsPerOrg: 4,
+ vaultGenericCredLibs: 20,
+ vaultGenericCredLibsDeleted: 5,
+ vaultSshCertCredLibs: 20,
+ vaultSshCertCredLibsDeleted: 5,
+ }
+
+ dataBeforeMigration, dataAfterMigration := testVaultCredLibRefactorDbSetup(t, c)
+ require.NotEmpty(dataBeforeMigration)
+ require.NotEmpty(dataAfterMigration)
+
+ // Validate that `testVaultCredLibRefactorDbSetup` seeded data correctly
+ // according to the count variables above.
+ require.Len(dataBeforeMigration.orgs, c.orgs)
+ require.Len(dataBeforeMigration.projects, c.orgs*c.projectsPerOrg)
+ require.Len(dataBeforeMigration.credentialStores, c.orgs*c.projectsPerOrg)
+ require.Len(dataBeforeMigration.credLibBase, (c.orgs*c.projectsPerOrg*c.vaultGenericCredLibs)+(c.orgs*c.projectsPerOrg*c.vaultSshCertCredLibs))
+ require.NotEmpty(dataBeforeMigration.credLibHistoryBase) // Some credential libraries have been updated so they'll have more than 1 history entry.
+ require.Len(dataBeforeMigration.postRefactorVaultCredLibBase, 0)
+ require.Len(dataBeforeMigration.vaultGenericCredLibs, c.orgs*c.projectsPerOrg*c.vaultGenericCredLibs)
+ require.Len(dataBeforeMigration.vaultSshCertCredLibs, c.orgs*c.projectsPerOrg*c.vaultSshCertCredLibs)
+ require.Len(dataBeforeMigration.deletedVaultGenericCredLibs, c.orgs*c.projectsPerOrg*c.vaultGenericCredLibsDeleted)
+ require.Len(dataBeforeMigration.deletedVaultSshCertCredLibs, c.orgs*c.projectsPerOrg*c.vaultSshCertCredLibsDeleted)
+
+ // Validate migrated data. Given that we asserted the data before migration
+ // is correctly seeded, we can just assert that post-migration and
+ // pre-migration elements match.
+ require.ElementsMatch(dataBeforeMigration.orgs, dataAfterMigration.orgs)
+ require.ElementsMatch(dataBeforeMigration.projects, dataAfterMigration.projects)
+ require.ElementsMatch(dataBeforeMigration.credentialStores, dataAfterMigration.credentialStores)
+ require.ElementsMatch(dataBeforeMigration.credLibBase, dataAfterMigration.credLibBase)
+ require.ElementsMatch(dataBeforeMigration.credLibHistoryBase, dataAfterMigration.credLibHistoryBase)
+ require.ElementsMatch(dataBeforeMigration.credLibBase, dataAfterMigration.postRefactorVaultCredLibBase) // We fill new credential_vault_library with data from credential_library.
+ require.ElementsMatch(dataBeforeMigration.vaultGenericCredLibs, dataAfterMigration.vaultGenericCredLibs)
+ require.ElementsMatch(dataBeforeMigration.vaultSshCertCredLibs, dataAfterMigration.vaultSshCertCredLibs)
+ require.ElementsMatch(dataBeforeMigration.deletedVaultGenericCredLibs, dataAfterMigration.deletedVaultGenericCredLibs)
+ require.ElementsMatch(dataBeforeMigration.deletedVaultSshCertCredLibs, dataAfterMigration.deletedVaultSshCertCredLibs)
+
+ // Finally, check table and view row counts.
+ require.EqualValues(dataBeforeMigration.rowCounts.credLib, dataAfterMigration.rowCounts.credLib)
+ require.EqualValues(dataBeforeMigration.rowCounts.credLibHistory, dataAfterMigration.rowCounts.credLibHistory)
+ require.EqualValues(dataBeforeMigration.rowCounts.credLib, dataAfterMigration.rowCounts.credVaultLibPostRefactor) // We fill new credential_vault_library from credential_library.
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibMappingOvrd, dataAfterMigration.rowCounts.credVaultGenericLibMappingOvrd)
+
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLib, dataAfterMigration.rowCounts.credVaultGenericLib)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibHistory, dataAfterMigration.rowCounts.credVaultGenericLibHistory)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibDeleted, dataAfterMigration.rowCounts.credVaultGenericLibDeleted)
+
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultSshCertLib, dataAfterMigration.rowCounts.credVaultSshCertLib)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultSshCertLibHistory, dataAfterMigration.rowCounts.credVaultSshCertLibHistory)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultSshCertLibDeleted, dataAfterMigration.rowCounts.credVaultSshCertLibDeleted)
+
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibMappingOvrdUserPass, dataAfterMigration.rowCounts.credVaultGenericLibMappingOvrdUserPass)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibMappingOvrdUserPassDomain, dataAfterMigration.rowCounts.credVaultGenericLibMappingOvrdUserPassDomain)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibMappingOvrdSshPk, dataAfterMigration.rowCounts.credVaultGenericLibMappingOvrdSshPk)
+
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultLibIssueCredentials, dataAfterMigration.rowCounts.credVaultLibIssueCredentials)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibListLookup, dataAfterMigration.rowCounts.credVaultGenericLibListLookup)
+ require.EqualValues(dataBeforeMigration.rowCounts.credVaultGenericLibHistoryAgg, dataAfterMigration.rowCounts.credVaultGenericLibHistoryAgg)
+ require.EqualValues(dataBeforeMigration.rowCounts.credDimensionSource, dataAfterMigration.rowCounts.credDimensionSource)
+}
+
+// testVaultCredLibRefactorDbSetup sets up a Boundary database running a prior
+// migration with credential library data, then migrates to the refactored
+// version. Not designed to be called outside of TestVaultCredLibRefactor.
+func testVaultCredLibRefactorDbSetup(t testing.TB, counts testVaultCredLibRefactorDbSetupCounts) (beforeMigration, afterMigration dbData) {
+ require := require.New(t)
+
+ const priorMigration = 98006
+ const credlibRefactorMigration = 99001
+
+ dialect := dbtest.Postgres
+ c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
+ require.NoError(err)
+ t.Cleanup(func() { require.NoError(c()) })
+
+ db, err := common.SqlOpen(dialect, u)
+ require.NoError(err)
+
+ // Migrate to the prior migration (before the one we want to test).
+ m, err := schema.NewManager(t.Context(), schema.Dialect(dialect), db, schema.WithEditions(
+ schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": priorMigration}),
+ ))
+ require.NoError(err)
+
+ _, err = m.ApplyMigrations(t.Context())
+ require.NoError(err)
+
+ state, err := m.CurrentState(t.Context())
+ require.NoError(err)
+ require.Equal(&schema.State{
+ Initialized: true,
+ Editions: []schema.EditionState{
+ {
+ Name: "oss",
+ BinarySchemaVersion: priorMigration,
+ DatabaseSchemaVersion: priorMigration,
+ DatabaseSchemaState: schema.Equal,
+ },
+ },
+ }, state)
+
+ tx, err := db.BeginTx(t.Context(), &sql.TxOptions{})
+ require.NoError(err)
+ require.NotNil(tx)
+
+ // Queries to seed all the required data.
+ createOrgScopeQuery := `
+ insert into iam_scope
+ (parent_id, type, public_id, name)
+ values
+ ('global', 'org', $1, $2);
+ `
+ createProjectScopeQuery := `
+ insert into iam_scope
+ (parent_id, type, public_id, name)
+ values
+ ( $1, 'project', $2, $3);
+ `
+ createCredStoreQuery := `
+ insert into credential_vault_store
+ (public_id, project_id, name, vault_address)
+ values
+ ( $1, $2, $3, $4);
+ `
+ createVaultGenericCredLibQuery := `
+ insert into credential_vault_library
+ (public_id, store_id, project_id, name, description, credential_type, vault_path, http_method, http_request_body)
+ values
+ ( $1, $2, $3, $4, $5, $6, $7, $8, $9);
+ `
+ updateVaultGenericCredLibQuery := `
+ update credential_vault_library
+ set name = $1,
+ description = $2,
+ vault_path = $3
+ where public_id = $4;
+ `
+ deleteVaultGenericCredLibQuery := `
+ delete from credential_vault_library
+ where public_id = $1;
+ `
+ createUpMappingOvrdQuery := `
+ insert into credential_vault_library_username_password_mapping_override
+ (library_id, username_attribute, password_attribute)
+ values
+ ( $1, $2, $3);
+ `
+ createUpdMappingOvrdQuery := `
+ insert into credential_vault_library_username_password_domain_mapping_ovrd
+ (library_id, username_attribute, password_attribute, domain_attribute)
+ values
+ ( $1, $2, $3, $4);
+ `
+ createSshPkMappingOvrdQuery := `
+ insert into credential_vault_library_ssh_private_key_mapping_override
+ (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute)
+ values
+ ( $1, $2, $3, $4);
+ `
+ createVaultSshCertCredLibQuery := `
+ insert into credential_vault_ssh_cert_library
+ (public_id, store_id, project_id, name, description, credential_type, vault_path, username, key_type, key_bits, ttl, key_id, critical_options, extensions, additional_valid_principals)
+ values
+ ( $1, $2, $3, $4, $5, 'ssh_certificate', $6, $7, $8, $9, $10, $11, $12, $13, $14);
+ `
+ updateVaultSshCertCredLibQuery := `
+ update credential_vault_ssh_cert_library
+ set name = $1,
+ description = $2,
+ vault_path = $3,
+ username = $4
+ where public_id = $5;
+ `
+ deleteVaultSshCertCredLibQuery := `
+ delete from credential_vault_ssh_cert_library
+ where public_id = $1;
+ `
+
+ execOk := func(res sql.Result, err error) {
+ // Exec returned no error.
+ require.NoError(err)
+ require.NotNil(res)
+
+ // Exec affected at least 1 row.
+ ra, err := res.RowsAffected()
+ require.NoError(err)
+ require.Greater(ra, int64(0))
+ }
+
+ // Create org scopes.
+ for range counts.orgs {
+ orgId := fmt.Sprintf("o_%s", base62.MustRandom(10))
+ orgName := fmt.Sprintf("Test %q Org Scope", orgId)
+ execOk(tx.ExecContext(t.Context(), createOrgScopeQuery, orgId, orgName))
+
+ // For each org scope, create `projectsPerOrgCount` project scopes and a
+ // Vault credential store.
+ for range counts.projectsPerOrg {
+ projectId := fmt.Sprintf("p_%s", base62.MustRandom(10))
+ projectName := fmt.Sprintf("Test %q Project Scope", projectId)
+ execOk(tx.ExecContext(t.Context(), createProjectScopeQuery, orgId, projectId, projectName))
+
+ credStoreId := fmt.Sprintf("csst_%s", base62.MustRandom(10))
+ credStoreName := fmt.Sprintf("Test %q Vault Credential Store", credStoreId)
+ execOk(tx.ExecContext(t.Context(), createCredStoreQuery, credStoreId, projectId, credStoreName, "http://127.0.0.1:8200"))
+
+ // For each credential store, create `vaultGenericCredLibs` Vault
+ // generic credential libraries with differing configurations and
+ // mapping overrides. Add `vaultGenericCredLibsDeleted` to account
+ // for the ones we have to create to delete.
+ deletedVaultGenericCredLibs := 0
+ for range counts.vaultGenericCredLibs + counts.vaultGenericCredLibsDeleted {
+ vgCredLibId := fmt.Sprintf("clvlt_%s", base62.MustRandom(10))
+ vgCredLibName := fmt.Sprintf("Test %q Vault Generic Credential Library", base62.MustRandom(10))
+
+ var vgCredType string
+ switch rand.IntN(4) {
+ case 0:
+ vgCredType = "unspecified"
+ case 1:
+ vgCredType = "username_password"
+ case 2:
+ vgCredType = "username_password_domain"
+ case 3:
+ vgCredType = "ssh_private_key"
+ }
+
+ var vgHttpMethod string
+ var vgHttpReqBody []byte
+ switch rand.IntN(2) {
+ case 0:
+ vgHttpMethod = "GET"
+ case 1:
+ vgHttpMethod = "POST"
+ vgHttpReqBody = []byte(`{"my_custom_request_body":true, "data": "this_is_some_data"}`)
+ }
+
+ execOk(tx.ExecContext(t.Context(), createVaultGenericCredLibQuery,
+ vgCredLibId, credStoreId, projectId, vgCredLibName, vgCredLibName,
+ vgCredType, "/my/vg/vault/path", vgHttpMethod, vgHttpReqBody,
+ ))
+
+ var vgMappingOverrideQuery string
+ vgMappingOverrideArgs := []any{vgCredLibId}
+ switch vgCredType {
+ case "unspecified": // No mapping override.
+ case "username_password":
+ vgMappingOverrideQuery = createUpMappingOvrdQuery
+ vgMappingOverrideArgs = append(vgMappingOverrideArgs, "username_ovrd", "password_ovrd")
+ case "username_password_domain":
+ vgMappingOverrideQuery = createUpdMappingOvrdQuery
+ vgMappingOverrideArgs = append(vgMappingOverrideArgs, "username_ovrd", "password_ovrd", "domain_ovrd")
+ case "ssh_private_key":
+ vgMappingOverrideQuery = createSshPkMappingOvrdQuery
+ vgMappingOverrideArgs = append(vgMappingOverrideArgs, "username_ovrd", "ssh_pk_ovrd", "ssh_pk_passphrase_ovrd")
+ }
+ if vgMappingOverrideQuery != "" {
+ execOk(tx.ExecContext(t.Context(), vgMappingOverrideQuery, vgMappingOverrideArgs...))
+ }
+
+ // Chance of updating the credential library.
+ switch rand.IntN(10) {
+ case 0:
+ execOk(tx.ExecContext(t.Context(), updateVaultGenericCredLibQuery, "Updated"+vgCredLibName,
+ "Updated"+vgCredLibName, "/my/updated/vault/path", vgCredLibId))
+ }
+
+ if counts.vaultGenericCredLibsDeleted > deletedVaultGenericCredLibs {
+ execOk(tx.ExecContext(t.Context(), deleteVaultGenericCredLibQuery, vgCredLibId))
+ deletedVaultGenericCredLibs++
+ }
+ }
+
+ // For each credential store, create `vaultSshCertCredLibs` Vault
+ // SSH Certificate credential libraries with different
+ // configurations. Add `vaultSshCertCredLibsDeleted` to account for
+ // the ones we have to create to delete.
+ deletedSshCertCredLibs := 0
+ for range counts.vaultSshCertCredLibs + counts.vaultSshCertCredLibsDeleted {
+ vSshCertCredLibId := fmt.Sprintf("clvsclt_%s", base62.MustRandom(10))
+ vSshCertCredLibName := fmt.Sprintf("Test %q Vault SSH Certificate Credential Library", base62.MustRandom(10))
+
+ var keyType string
+ var keyBits int
+ switch rand.IntN(3) {
+ case 0:
+ keyType = "ed25519"
+ keyBits = 0
+ case 1:
+ keyType = "ecdsa"
+ keyBits = 521
+ case 2:
+ keyType = "rsa"
+ keyBits = 4096
+ }
+
+ var ttl string
+ switch rand.IntN(2) {
+ case 0: // No TTL.
+ case 1:
+ ttl = strconv.FormatInt(rand.Int64N(65536), 10)
+ }
+
+ var keyId string
+ switch rand.IntN(2) {
+ case 0: // No key id.
+ case 1:
+ keyId = "my_key_id"
+ }
+
+ var criticalOptions []byte
+ switch rand.IntN(2) {
+ case 0: // No Critical options.
+ case 1:
+ criticalOptions = []byte(`critical_option1=yes, critical_option2=alsoyes, critical_option3=yep`)
+ }
+
+ var extensions []byte
+ switch rand.IntN(2) {
+ case 0: // No extensions.
+ case 1:
+ extensions = []byte(`extension1=yes, extension2=alsoyes, extension3=yep`)
+ }
+
+ var additionalPrincipals string
+ switch rand.IntN(2) {
+ case 0: // No additional principals.
+ case 1:
+ additionalPrincipals = "principal1, principal2, principal3"
+ }
+
+ execOk(tx.ExecContext(t.Context(), createVaultSshCertCredLibQuery,
+ vSshCertCredLibId, credStoreId, projectId, vSshCertCredLibName,
+ vSshCertCredLibName, "/ssh/sign/cert1", "username", keyType,
+ keyBits, ttl, keyId, criticalOptions, extensions, additionalPrincipals,
+ ))
+ require.NoError(err)
+
+ // Chance of updating credential library.
+ switch rand.IntN(10) {
+ case 0:
+ execOk(tx.ExecContext(t.Context(), updateVaultSshCertCredLibQuery, "Updated"+vSshCertCredLibName,
+ "Updated"+vSshCertCredLibName, "/ssh/issue/updated_cert1", "updated_username", vSshCertCredLibId))
+ }
+
+ if counts.vaultSshCertCredLibsDeleted > deletedSshCertCredLibs {
+ execOk(tx.ExecContext(t.Context(), deleteVaultSshCertCredLibQuery, vSshCertCredLibId))
+ deletedSshCertCredLibs++
+ }
+ }
+ }
+ }
+ err = tx.Commit()
+ require.NoError(err)
+
+ // Get a snapshot of what the data looks like before the migration.
+ beforeMigration = testBuildTestVaultCredLibRefactorDbData(t, db, false)
+
+ // Now we're ready for the migration we want to test.
+ m, err = schema.NewManager(t.Context(), schema.Dialect(dialect), db, schema.WithEditions(
+ schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": credlibRefactorMigration}),
+ ))
+ require.NoError(err)
+
+ _, err = m.ApplyMigrations(t.Context())
+ require.NoError(err)
+
+ state, err = m.CurrentState(t.Context())
+ require.NoError(err)
+ require.Equal(&schema.State{
+ Initialized: true,
+ Editions: []schema.EditionState{
+ {
+ Name: "oss",
+ BinarySchemaVersion: credlibRefactorMigration,
+ DatabaseSchemaVersion: credlibRefactorMigration,
+ DatabaseSchemaState: schema.Equal,
+ },
+ },
+ }, state)
+
+ // Get a snapshot of the data after the migration.
+ afterMigration = testBuildTestVaultCredLibRefactorDbData(t, db, true)
+
+ return beforeMigration, afterMigration
+}
+
+// testBuildTestVaultCredLibRefactorDbData will build a 'dbData' object from
+// database queries. Accepts a boolean that denotes if any given call is
+// post-refactor. 'false' means assume before refactor migration; 'true' means
+// assume after refactor migration. This boolean controls table names on various
+// queries that retrieve data.
+func testBuildTestVaultCredLibRefactorDbData(t testing.TB, db *sql.DB, afterRefactor bool) dbData {
+ require := require.New(t)
+
+ queryContext := func(tx *sql.Tx, query string, callbackFn func(scanFn func(dest ...any) error)) {
+ rows, err := tx.QueryContext(t.Context(), query)
+ require.NoError(err)
+ require.NotNil(rows)
+
+ for rows.Next() {
+ callbackFn(rows.Scan)
+ }
+ require.NoError(rows.Err())
+ }
+
+ data := dbData{
+ orgs: make([]orgProj, 0),
+ projects: make([]orgProj, 0),
+ credentialStores: make([]vaultCredStore, 0),
+ credLibBase: make([]credLibBase, 0),
+ credLibHistoryBase: make([]credLibHistoryBase, 0),
+ postRefactorVaultCredLibBase: make([]credLibBase, 0),
+ vaultGenericCredLibs: make([]vaultGenericCredLib, 0),
+ vaultSshCertCredLibs: make([]vaultSshCertCredLib, 0),
+ deletedVaultGenericCredLibs: make([]credLibDeleted, 0),
+ deletedVaultSshCertCredLibs: make([]credLibDeleted, 0),
+ rowCounts: rowCounts{},
+ }
+
+ tx, err := db.BeginTx(t.Context(), &sql.TxOptions{})
+ require.NoError(err)
+ require.NotNil(tx)
+ defer func() { _ = tx.Rollback() }()
+
+ // Get org and project scopes.
+ queryContext(tx, "select * from iam_scope", func(scanFn func(dest ...any) error) {
+ var publicId, name, typ, description, parentId, primaryAuthMethodId *string
+ var createTime, updateTime *time.Time
+ var version *int
+ require.NoError(scanFn(&publicId, &createTime, &updateTime, &name,
+ &typ, &description, &parentId, &version, &primaryAuthMethodId),
+ )
+
+ op := orgProj{
+ publicId: testDeref(t, publicId),
+ name: testDeref(t, name),
+ description: testDeref(t, description),
+ createTime: testDeref(t, createTime),
+ updateTime: testDeref(t, updateTime),
+ typ: testDeref(t, typ),
+ parentId: testDeref(t, parentId),
+ version: testDeref(t, version),
+ }
+ switch op.typ {
+ case "global":
+ case "org":
+ data.orgs = append(data.orgs, op)
+ case "project":
+ data.projects = append(data.projects, op)
+ default:
+ require.Failf("failed to get org/project scopes", "unknown scope type %s", op.typ)
+ }
+ })
+
+ // Get credential stores.
+ queryContext(tx, "select * from credential_vault_store", func(scanFn func(dest ...any) error) {
+ var publicId, projectId, name, description, vaultAddr, namespace,
+ tlsServerName, workerFilter *string
+ var createTime, updateTime, deleteTime *time.Time
+ var version *int
+ var caCert *[]byte
+ var tlsSkipVerify *bool
+
+ require.NoError(scanFn(&publicId, &projectId, &name, &description,
+ &createTime, &updateTime, &deleteTime, &version, &vaultAddr, &namespace,
+ &caCert, &tlsServerName, &tlsSkipVerify, &workerFilter),
+ )
+
+ data.credentialStores = append(data.credentialStores, vaultCredStore{
+ publicId: testDeref(t, publicId),
+ projectId: testDeref(t, projectId),
+ name: testDeref(t, name),
+ description: testDeref(t, description),
+ createTime: testDeref(t, createTime),
+ updateTime: testDeref(t, updateTime),
+ deleteTime: testDeref(t, deleteTime),
+ version: testDeref(t, version),
+ vaultAddr: testDeref(t, vaultAddr),
+ namespace: testDeref(t, namespace),
+ caCert: testDeref(t, caCert),
+ tlsServerName: testDeref(t, tlsServerName),
+ tlsSkipVerify: testDeref(t, tlsSkipVerify),
+ workerFilter: testDeref(t, workerFilter),
+ })
+ })
+
+ // Get credential library base table.
+ queryContext(tx, "select * from credential_library", func(scanFn func(dest ...any) error) {
+ var publicId, storeId, credType, projectId *string
+ var createTime, updateTime *time.Time
+ require.NoError(scanFn(&publicId, &storeId, &credType, &projectId, &createTime, &updateTime))
+
+ data.credLibBase = append(data.credLibBase, credLibBase{
+ publicId: testDeref(t, publicId),
+ storeId: testDeref(t, storeId),
+ credType: testDeref(t, credType),
+ projectId: testDeref(t, projectId),
+ createTime: testDeref(t, createTime),
+ updateTime: testDeref(t, updateTime),
+ })
+ })
+
+ // Get credential library base table if we're post-refactor.
+ if afterRefactor {
+ queryContext(tx, "select * from credential_vault_library", func(scanFn func(dest ...any) error) {
+ var publicId, storeId, credType, projectId *string
+ var createTime, updateTime *time.Time
+ require.NoError(scanFn(&publicId, &storeId, &credType, &projectId, &createTime, &updateTime))
+
+ data.postRefactorVaultCredLibBase = append(data.postRefactorVaultCredLibBase, credLibBase{
+ publicId: testDeref(t, publicId),
+ storeId: testDeref(t, storeId),
+ credType: testDeref(t, credType),
+ projectId: testDeref(t, projectId),
+ createTime: testDeref(t, createTime),
+ updateTime: testDeref(t, updateTime),
+ })
+ })
+ }
+
+ // Get credential library history base table.
+ queryContext(tx, "select * from credential_library_history_base", func(scanFn func(dest ...any) error) {
+ var historyId *string
+ require.NoError(scanFn(&historyId))
+ data.credLibHistoryBase = append(data.credLibHistoryBase, credLibHistoryBase{testDeref(t, historyId)})
+ })
+
+ // Get Vault generic credential libraries.
+ vaultGenericCredLibTableName := "credential_vault_library"
+ if afterRefactor {
+ vaultGenericCredLibTableName = "credential_vault_generic_library"
+ }
+ queryContext(tx, fmt.Sprintf("select * from %s", vaultGenericCredLibTableName), func(scanFn func(dest ...any) error) {
+ var publicId, storeId, name, description, vaultPath, httpMethod,
+ credType, projectId *string
+ var createTime, updateTime *time.Time
+ var version *int
+ var httpReqBody *[]byte
+
+ require.NoError(scanFn(&publicId, &storeId, &name, &description,
+ &createTime, &updateTime, &version, &vaultPath, &httpMethod,
+ &httpReqBody, &credType, &projectId),
+ )
+
+ data.vaultGenericCredLibs = append(data.vaultGenericCredLibs, vaultGenericCredLib{
+ publicId: testDeref(t, publicId),
+ storeId: testDeref(t, storeId),
+ name: testDeref(t, name),
+ description: testDeref(t, description),
+ createTime: testDeref(t, createTime),
+ updateTime: testDeref(t, updateTime),
+ version: testDeref(t, version),
+ vaultPath: testDeref(t, vaultPath),
+ httpMethod: testDeref(t, httpMethod),
+ httpReqBody: testDeref(t, httpReqBody),
+ credType: testDeref(t, credType),
+ projectId: testDeref(t, projectId),
+
+ mappingOverride: vaultGenericCredLibMappingOvrd{}, // Fetch below.
+ history: make([]vaultGenericCredLibHistory, 0), // Fetch below.
+ })
+ })
+
+ // Get mapping overrides for Vault generic credential libraries.
+ upMappingOvrdTableName := "credential_vault_library_username_password_mapping_override"
+ updMappingOvrdTableName := "credential_vault_library_username_password_domain_mapping_ovrd"
+ sshPkMappingOvrdTableName := "credential_vault_library_ssh_private_key_mapping_override"
+ if afterRefactor {
+ upMappingOvrdTableName = "credential_vault_generic_library_username_password_mapping_ovrd"
+ updMappingOvrdTableName = "credential_vault_generic_library_usern_pass_domain_mapping_ovrd"
+ sshPkMappingOvrdTableName = "credential_vault_generic_library_ssh_private_key_mapping_ovrd"
+ }
+ for _, cl := range data.vaultGenericCredLibs {
+ queryFmt := "select * from %s where library_id = '%s'"
+ getMappingOvrd := false
+ switch cl.credType {
+ case "unspecified":
+ case "username_password":
+ queryFmt = fmt.Sprintf(queryFmt, upMappingOvrdTableName, cl.publicId)
+ getMappingOvrd = true
+ case "username_password_domain":
+ queryFmt = fmt.Sprintf(queryFmt, updMappingOvrdTableName, cl.publicId)
+ getMappingOvrd = true
+ case "ssh_private_key":
+ queryFmt = fmt.Sprintf(queryFmt, sshPkMappingOvrdTableName, cl.publicId)
+ getMappingOvrd = true
+ default:
+ require.Failf("failed to get vault generic mapping overrides", "unknown credential type %s", cl.credType)
+ }
+ if getMappingOvrd {
+ queryContext(tx, queryFmt, func(scanFn func(dest ...any) error) {
+ var libraryId, usernameAttr, passwordAttr, domainAttr, pkAttr,
+ pkPassphraseAttr *string
+
+ switch cl.credType {
+ case "username_password":
+ require.NoError(scanFn(&libraryId, &usernameAttr, &passwordAttr))
+ case "username_password_domain":
+ require.NoError(scanFn(&libraryId, &usernameAttr, &passwordAttr, &domainAttr))
+ case "ssh_private_key":
+ require.NoError(scanFn(&libraryId, &usernameAttr, &pkAttr, &pkPassphraseAttr))
+ }
+
+ cl.mappingOverride = vaultGenericCredLibMappingOvrd{
+ libraryId: testDeref(t, libraryId),
+ usernameAttr: testDeref(t, usernameAttr),
+ passwordAttr: testDeref(t, passwordAttr),
+ domainAttr: testDeref(t, domainAttr),
+ pkAttr: testDeref(t, pkAttr),
+ pkPassphraseAttr: testDeref(t, pkPassphraseAttr),
+ }
+ })
+ }
+ }
+
+ // Get history for Vault generic credential libraries.
+ for _, cl := range data.vaultGenericCredLibs {
+ hstTableName := "credential_vault_library_hst"
+ if afterRefactor {
+ hstTableName = "credential_vault_generic_library_hst"
+ }
+
+ query := fmt.Sprintf("select * from %s where public_id = '%s'", hstTableName, cl.publicId)
+ queryContext(tx, query, func(scanFn func(dest ...any) error) {
+ var publicId, name, description, projectId, storeId, vaultPath,
+ httpMethod, credType, historyId, validRange *string
+ var httpReqBody *[]byte
+
+ require.NoError(scanFn(&publicId, &name, &description, &projectId,
+ &storeId, &vaultPath, &httpMethod, &httpReqBody, &credType,
+ &historyId, &validRange),
+ )
+
+ cl.history = append(cl.history, vaultGenericCredLibHistory{
+ historyId: testDeref(t, historyId),
+ validRange: testDeref(t, validRange),
+ cl: vaultGenericCredLib{
+ publicId: testDeref(t, publicId),
+ name: testDeref(t, name),
+ description: testDeref(t, description),
+ projectId: testDeref(t, projectId),
+ storeId: testDeref(t, storeId),
+ vaultPath: testDeref(t, vaultPath),
+ httpMethod: testDeref(t, httpMethod),
+ httpReqBody: testDeref(t, httpReqBody),
+ credType: testDeref(t, credType),
+ },
+ })
+ })
+ }
+
+ // Get Vault SSH certificate credential libraries.
+ queryContext(tx, "select * from credential_vault_ssh_cert_library", func(scanFn func(dest ...any) error) {
+ var publicId, storeId, name, description, vaultPath, username, keyType,
+ ttl, keyId, credType, projectId, additionalPrincipals *string
+ var createTime, updateTime *time.Time
+ var version, keyBits *int
+ var criticalOptions, extensions *[]byte
+
+ require.NoError(scanFn(&publicId, &storeId, &name, &description,
+ &createTime, &updateTime, &version, &vaultPath, &username, &keyType,
+ &keyBits, &ttl, &keyId, &criticalOptions, &extensions, &credType,
+ &projectId, &additionalPrincipals),
+ )
+
+ data.vaultSshCertCredLibs = append(data.vaultSshCertCredLibs, vaultSshCertCredLib{
+ publicId: testDeref(t, publicId),
+ storeId: testDeref(t, storeId),
+ name: testDeref(t, name),
+ description: testDeref(t, description),
+ createTime: testDeref(t, createTime),
+ updateTime: testDeref(t, updateTime),
+ version: testDeref(t, version),
+ vaultPath: testDeref(t, vaultPath),
+ username: testDeref(t, username),
+ keyType: testDeref(t, keyType),
+ keyBits: testDeref(t, keyBits),
+ ttl: testDeref(t, ttl),
+ keyId: testDeref(t, keyId),
+ criticalOptions: testDeref(t, criticalOptions),
+ extensions: testDeref(t, extensions),
+ credType: testDeref(t, credType),
+ projectId: testDeref(t, projectId),
+ additionalValidPrincipals: testDeref(t, additionalPrincipals),
+
+ history: make([]vaultSshCertCredLibHistory, 0), // Fetch below.
+ })
+ })
+
+ // Get history for Vault SSH certificate credential libraries.
+ for _, cl := range data.vaultSshCertCredLibs {
+ query := fmt.Sprintf("select * from credential_vault_ssh_cert_library_hst where public_id = '%s'", cl.publicId)
+ queryContext(tx, query, func(scanFn func(dest ...any) error) {
+ var publicId, storeId, name, description, vaultPath, username, keyType,
+ ttl, credType, projectId, historyId, validRange *string
+ var keyBits *int
+ var criticalOptions, extensions *[]byte
+
+ require.NoError(scanFn(&publicId, &name, &description, &projectId,
+ &storeId, &vaultPath, &username, &keyType, &keyBits, &ttl,
+ &criticalOptions, &extensions, &credType, &historyId, &validRange),
+ )
+
+ cl.history = append(cl.history, vaultSshCertCredLibHistory{
+ historyId: *historyId,
+ validRange: *validRange,
+ cl: vaultSshCertCredLib{
+ publicId: testDeref(t, publicId),
+ name: testDeref(t, name),
+ description: testDeref(t, description),
+ projectId: testDeref(t, projectId),
+ storeId: testDeref(t, storeId),
+ vaultPath: testDeref(t, vaultPath),
+ username: testDeref(t, username),
+ keyType: testDeref(t, keyType),
+ keyBits: testDeref(t, keyBits),
+ ttl: testDeref(t, ttl),
+ criticalOptions: testDeref(t, criticalOptions),
+ extensions: testDeref(t, extensions),
+ credType: testDeref(t, credType),
+ },
+ })
+ })
+ }
+
+ // Get all deleted vault credential libraries.
+ vaultGenericCredLibDeletedTableName := "credential_vault_library_deleted"
+ if afterRefactor {
+ vaultGenericCredLibDeletedTableName = "credential_vault_generic_library_deleted"
+ }
+ vaultSshCertCredLibDeletedTableName := "credential_vault_ssh_cert_library_deleted"
+ deletedTables := []string{vaultGenericCredLibDeletedTableName, vaultSshCertCredLibDeletedTableName}
+ for _, dt := range deletedTables {
+ queryContext(tx, fmt.Sprintf("select * from %s", dt), func(scanFn func(dest ...any) error) {
+ var publicId *string
+ var deleteTime *time.Time
+
+ require.NoError(scanFn(&publicId, &deleteTime))
+
+ cld := credLibDeleted{
+ publicId: testDeref(t, publicId),
+ deleteTime: testDeref(t, deleteTime),
+ }
+ switch dt {
+ case vaultGenericCredLibDeletedTableName:
+ data.deletedVaultGenericCredLibs = append(data.deletedVaultGenericCredLibs, cld)
+ case vaultSshCertCredLibDeletedTableName:
+ data.deletedVaultSshCertCredLibs = append(data.deletedVaultSshCertCredLibs, cld)
+ }
+ })
+ }
+
+ // Get row counts.
+ var rowCountQuery string
+ if !afterRefactor {
+ rowCountQuery = `
+ select
+ ( select count(*) from credential_library ),
+ ( select count(*) from credential_library_history_base ),
+ ( select count(*) from credential_vault_library_mapping_override ),
+
+ ( select count(*) from credential_vault_library ),
+ ( select count(*) from credential_vault_library_hst ),
+ ( select count(*) from credential_vault_library_deleted ),
+
+ ( select count(*) from credential_vault_ssh_cert_library ),
+ ( select count(*) from credential_vault_ssh_cert_library_hst ),
+ ( select count(*) from credential_vault_ssh_cert_library_deleted ),
+
+ ( select count(*) from credential_vault_library_username_password_mapping_override ),
+ ( select count(*) from credential_vault_library_username_password_domain_mapping_ovrd ),
+ ( select count(*) from credential_vault_library_ssh_private_key_mapping_override ),
+
+ ( select count(*) from credential_vault_library_issue_credentials ),
+ ( select count(*) from credential_vault_library_list_lookup ),
+ ( select count(*) from credential_vault_library_hst_aggregate ),
+ ( select count(*) from whx_credential_dimension_source );
+ `
+ } else {
+ rowCountQuery = `
+ select
+ ( select count(*) from credential_library ),
+ ( select count(*) from credential_library_history_base ),
+ ( select count(*) from credential_vault_generic_library_mapping_override ),
+ ( select count(*) from credential_vault_library ),
+
+ ( select count(*) from credential_vault_generic_library ),
+ ( select count(*) from credential_vault_generic_library_hst ),
+ ( select count(*) from credential_vault_generic_library_deleted ),
+
+ ( select count(*) from credential_vault_ssh_cert_library ),
+ ( select count(*) from credential_vault_ssh_cert_library_hst ),
+ ( select count(*) from credential_vault_ssh_cert_library_deleted ),
+
+ ( select count(*) from credential_vault_generic_library_username_password_mapping_ovrd ),
+ ( select count(*) from credential_vault_generic_library_usern_pass_domain_mapping_ovrd ),
+ ( select count(*) from credential_vault_generic_library_ssh_private_key_mapping_ovrd ),
+
+ ( select count(*) from credential_vault_library_issue_credentials ),
+ ( select count(*) from credential_vault_generic_library_list_lookup ),
+ ( select count(*) from credential_vault_generic_library_hst_aggregate ),
+ ( select count(*) from whx_credential_dimension_source );
+ `
+ }
+ queryContext(tx, rowCountQuery, func(scanFn func(dest ...any) error) {
+ var cl, clHistBase, clVaultBasePostRefactorOnly, clVaultGenericMappingOvrd,
+ clVaultGeneric, clVaultGenericHist, clVaultGenericDel, clVaultSshCert,
+ clVaultSshCertHist, clVaultSshCertDel, clVaultGenericUpMappingOvrd,
+ clVaultGenericUpdMappingOvrd, clVaultGenericSshPkMappingOverd,
+ clVaultLibIssueCreds, clVaultGenericListLookup, clVaultGenericHistAgg, credDimensionSource *int64
+
+ if !afterRefactor {
+ require.NoError(scanFn(&cl, &clHistBase, &clVaultGenericMappingOvrd,
+ &clVaultGeneric, &clVaultGenericHist, &clVaultGenericDel,
+ &clVaultSshCert, &clVaultSshCertHist, &clVaultSshCertDel,
+ &clVaultGenericUpMappingOvrd, &clVaultGenericUpdMappingOvrd, &clVaultGenericSshPkMappingOverd,
+ &clVaultLibIssueCreds, &clVaultGenericListLookup, &clVaultGenericHistAgg, &credDimensionSource),
+ )
+ } else {
+ require.NoError(scanFn(&cl, &clHistBase, &clVaultGenericMappingOvrd, &clVaultBasePostRefactorOnly,
+ &clVaultGeneric, &clVaultGenericHist, &clVaultGenericDel,
+ &clVaultSshCert, &clVaultSshCertHist, &clVaultSshCertDel,
+ &clVaultGenericUpMappingOvrd, &clVaultGenericUpdMappingOvrd, &clVaultGenericSshPkMappingOverd,
+ &clVaultLibIssueCreds, &clVaultGenericListLookup, &clVaultGenericHistAgg, &credDimensionSource),
+ )
+ }
+
+ data.rowCounts = rowCounts{
+ credLib: testDeref(t, cl),
+ credLibHistory: testDeref(t, clHistBase),
+ credVaultLibPostRefactor: testDeref(t, clVaultBasePostRefactorOnly),
+ credVaultGenericLibMappingOvrd: testDeref(t, clVaultGenericMappingOvrd),
+ credVaultGenericLib: testDeref(t, clVaultGeneric),
+ credVaultGenericLibHistory: testDeref(t, clVaultGenericHist),
+ credVaultGenericLibDeleted: testDeref(t, clVaultGenericDel),
+ credVaultSshCertLib: testDeref(t, clVaultSshCert),
+ credVaultSshCertLibHistory: testDeref(t, clVaultSshCertHist),
+ credVaultSshCertLibDeleted: testDeref(t, clVaultSshCertDel),
+ credVaultGenericLibMappingOvrdUserPass: testDeref(t, clVaultGenericUpMappingOvrd),
+ credVaultGenericLibMappingOvrdUserPassDomain: testDeref(t, clVaultGenericUpdMappingOvrd),
+ credVaultGenericLibMappingOvrdSshPk: testDeref(t, clVaultGenericSshPkMappingOverd),
+ credVaultLibIssueCredentials: testDeref(t, clVaultLibIssueCreds),
+ credVaultGenericLibListLookup: testDeref(t, clVaultGenericListLookup),
+ credVaultGenericLibHistoryAgg: testDeref(t, clVaultGenericHistAgg),
+ credDimensionSource: testDeref(t, credDimensionSource),
+ }
+ })
+
+ return data
+}
+
+// testDeref is a generic function that dereferences a pointer type,
+// safeguarding against a nil pointer dereference. If the input pointer is nil,
+// it returns the zero-value for the input type.
+func testDeref[T any](_ testing.TB, in *T) T {
+ if in == nil {
+ return *new(T)
+ }
+ return *in
+}
+
+// dbData is the aggregation of all the data that
+// testBuildTestVaultCredLibRefactorDbData gets together. Not designed to be
+// used outside of TestVaultCredLibRefactor.
+type dbData struct {
+ orgs []orgProj // From iam_scope.
+ projects []orgProj // From iam_scope.
+
+ credentialStores []vaultCredStore // From credential_vault_store.
+
+ credLibBase []credLibBase // From credential_library.
+ credLibHistoryBase []credLibHistoryBase // From credential_library_history_base.
+ postRefactorVaultCredLibBase []credLibBase // From credential_vault_library (after refactor only).
+
+ vaultGenericCredLibs []vaultGenericCredLib // From credential_vault_library (pre-refactor) or credential_vault_generic_library.
+ vaultSshCertCredLibs []vaultSshCertCredLib // From credential_vault_ssh_cert_library.
+
+ deletedVaultGenericCredLibs []credLibDeleted // From credential_vault_library_deleted (pre-refactor) or credential_vault_generic_library_deleted.
+ deletedVaultSshCertCredLibs []credLibDeleted // From credential_vault_ssh_cert_library_deleted.
+
+ rowCounts rowCounts
+}
+
+// orgProj holds database data about a org/project scope. Not designed to be
+// used outside of testBuildTestVaultCredLibRefactorDbData.
+type orgProj struct {
+ publicId string
+ name string
+ description string
+ createTime time.Time
+ updateTime time.Time
+ typ string
+ parentId string
+ version int
+}
+
+// credLibBase holds database data about a credential library.
+// Not designed to be used outside of testBuildTestVaultCredLibRefactorDbData.
+type credLibBase struct {
+ publicId string
+ storeId string
+ credType string
+ projectId string
+ createTime time.Time
+ updateTime time.Time
+}
+
+// credLibHistoryBase holds historical data about a credential library. Not
+// designed to be used outside of testBuildTestVaultCredLibRefactorDbData.
+type credLibHistoryBase struct {
+ historyId string
+}
+
+// credLibDeleted holds database data about a deleted credential library. Not
+// designed to be used outside of testBuildTestVaultCredLibRefactorDbData.
+type credLibDeleted struct {
+ publicId string
+ deleteTime time.Time
+}
+
+// vaultCredStore holds database data about a Vault credential store. Not
+// designed to be used outside of testBuildTestVaultCredLibRefactorDbData.
+type vaultCredStore struct {
+ publicId string
+ projectId string
+ name string
+ description string
+ createTime time.Time
+ updateTime time.Time
+ deleteTime time.Time
+ version int
+ vaultAddr string
+ namespace string
+ caCert []byte
+ tlsServerName string
+ tlsSkipVerify bool
+ workerFilter string
+}
+
+// vaultGenericCredLib holds database data about a Vault generic credential
+// library. Not designed to be used outside of
+// testBuildTestVaultCredLibRefactorDbData.
+type vaultGenericCredLib struct {
+ publicId string
+ storeId string
+ name string
+ description string
+ createTime time.Time
+ updateTime time.Time
+ version int
+ vaultPath string
+ httpMethod string
+ httpReqBody []byte
+ credType string
+ projectId string
+
+ mappingOverride vaultGenericCredLibMappingOvrd
+ history []vaultGenericCredLibHistory
+}
+type vaultGenericCredLibHistory struct {
+ cl vaultGenericCredLib
+ historyId string
+ validRange string
+}
+
+// vaultSshCertCredLib holds database data about a Vault SSH certificate
+// credential library. Not designed to be used outside of
+// testBuildTestVaultCredLibRefactorDbData.
+type vaultSshCertCredLib struct {
+ publicId string
+ storeId string
+ name string
+ description string
+ createTime time.Time
+ updateTime time.Time
+ version int
+ vaultPath string
+ username string
+ keyType string
+ keyBits int
+ ttl string
+ keyId string
+ criticalOptions []byte
+ extensions []byte
+ credType string
+ projectId string
+ additionalValidPrincipals string
+
+ history []vaultSshCertCredLibHistory
+}
+type vaultSshCertCredLibHistory struct {
+ cl vaultSshCertCredLib
+ historyId string
+ validRange string
+}
+
+// vaultGenericCredLibMappingOvrd holds database data about a Vault generic
+// credential library's mapping override. Not designed to be used outside of
+// testBuildTestVaultCredLibRefactorDbData.
+type vaultGenericCredLibMappingOvrd struct {
+ libraryId string
+ usernameAttr string
+ passwordAttr string
+ domainAttr string
+ pkAttr string
+ pkPassphraseAttr string
+}
+
+// rowCounts holds row count information for various database tables and views.
+// Not designed to be used outside of testBuildTestVaultCredLibRefactorDbData.
+type rowCounts struct {
+ // credential_library
+ credLib int64
+ // credential_library_history_base
+ credLibHistory int64
+ // credential_vault_library (post-refactor meaning only)
+ credVaultLibPostRefactor int64
+ // credential_vault_library_mapping_override (pre-refactor) or
+ // credential_vault_generic_library_mapping_override
+ credVaultGenericLibMappingOvrd int64
+
+ // credential_vault_library (pre-refactor) or
+ // credential_vault_generic_library
+ credVaultGenericLib int64
+ // credential_vault_library_hst (pre-refactor) or
+ // credential_vault_generic_library_hst
+ credVaultGenericLibHistory int64
+ // credential_vault_library_deleted (pre-refactor) or
+ // credential_vault_generic_library_deleted
+ credVaultGenericLibDeleted int64
+
+ // credential_vault_ssh_cert_library
+ credVaultSshCertLib int64
+ // credential_vault_ssh_cert_library_hst
+ credVaultSshCertLibHistory int64
+ // credential_vault_ssh_cert_library_deleted
+ credVaultSshCertLibDeleted int64
+
+ // credential_vault_library_username_password_mapping_override
+ // (pre-refactor) or
+ // credential_vault_generic_library_username_password_mapping_ovrd
+ credVaultGenericLibMappingOvrdUserPass int64
+ // credential_vault_library_username_password_domain_mapping_ovrd
+ // (pre-refactor) or
+ // credential_vault_generic_library_usern_pass_domain_mapping_ovrd
+ credVaultGenericLibMappingOvrdUserPassDomain int64
+ // credential_vault_library_ssh_private_key_mapping_override (pre-refactor)
+ // or credential_vault_generic_library_ssh_private_key_mapping_ovrd
+ credVaultGenericLibMappingOvrdSshPk int64
+
+ // credential_vault_library_issue_credentials
+ credVaultLibIssueCredentials int64
+
+ // credential_vault_library_list_lookup (pre-refactor), or
+ // credential_vault_generic_library_list_lookup
+ credVaultGenericLibListLookup int64
+
+ // credential_vault_library_hst_aggregate (pre-refactor), or
+ // credential_vault_generic_library_hst_aggregate
+ credVaultGenericLibHistoryAgg int64
+
+ // whx_credential_dimension_source
+ credDimensionSource int64
+}
+
+// testVaultCredLibRefactorDbSetupCounts is an input to
+// testVaultCredLibRefactorDbSetup that controls the number of
+// orgs/projects/credential libraries to create.
+type testVaultCredLibRefactorDbSetupCounts struct {
+ // orgs is the number of org scopes to create.
+ orgs int
+
+ // projectsPerOrg is the number of project scopes to create per org.
+ projectsPerOrg int
+
+ // vaultGenericCredLibs is the number of Vault generic credential
+ // libraries to create per project.
+ vaultGenericCredLibs int
+
+ // vaultGenericCredLibsDeleted is the number of Vault generic credential
+ // libraries to delete per project. Note that we create new credential
+ // libraries for the purposes of deletion so this doesn't affect
+ // vaultGenericCredLibs.
+ vaultGenericCredLibsDeleted int
+
+ // vaultSshCertCredLibs is the number of Vault SSH Certificate
+ // credential libraries to create per project.
+ vaultSshCertCredLibs int
+
+ // vaultSshCertCredLibsDeleted is the number of Vault SSH certificate
+ // credential libraries to delete per project. Note that we create new
+ // credential libraries for the purposes of deletion so this doesn't affect
+ // vaultSshCertCredLibs.
+ vaultSshCertCredLibsDeleted int
+}
diff --git a/internal/db/sqltest/initdb.d/01_colors_persona.sql b/internal/db/sqltest/initdb.d/01_colors_persona.sql
index f38d9650e..301da03b0 100644
--- a/internal/db/sqltest/initdb.d/01_colors_persona.sql
+++ b/internal/db/sqltest/initdb.d/01_colors_persona.sql
@@ -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
diff --git a/internal/db/sqltest/initdb.d/03_widgets_persona.sql b/internal/db/sqltest/initdb.d/03_widgets_persona.sql
index 01b63839d..58045eb2f 100644
--- a/internal/db/sqltest/initdb.d/03_widgets_persona.sql
+++ b/internal/db/sqltest/initdb.d/03_widgets_persona.sql
@@ -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');
diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_ldap_library.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_ldap_library.sql
new file mode 100644
index 000000000..c2f36757e
--- /dev/null
+++ b/internal/db/sqltest/tests/credential/vault/credential_vault_ldap_library.sql
@@ -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;
diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_library.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_library.sql
index ae78da439..5d04db1f8 100644
--- a/internal/db/sqltest/tests/credential/vault/credential_vault_library.sql
+++ b/internal/db/sqltest/tests/credential/vault/credential_vault_library.sql
@@ -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();
diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_library_issue_credentials.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_library_issue_credentials.sql
index 5248a335c..7af2f8add 100644
--- a/internal/db/sqltest/tests/credential/vault/credential_vault_library_issue_credentials.sql
+++ b/internal/db/sqltest/tests/credential/vault/credential_vault_library_issue_credentials.sql
@@ -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
diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_library_refactor.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_library_refactor.sql
new file mode 100644
index 000000000..f015f0efb
--- /dev/null
+++ b/internal/db/sqltest/tests/credential/vault/credential_vault_library_refactor.sql
@@ -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;
diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_library_ssh_private_key_mapping_override.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_library_ssh_private_key_mapping_override.sql
index 011c7c153..84b1af3d8 100644
--- a/internal/db/sqltest/tests/credential/vault/credential_vault_library_ssh_private_key_mapping_override.sql
+++ b/internal/db/sqltest/tests/credential/vault/credential_vault_library_ssh_private_key_mapping_override.sql
@@ -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;
diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_domain_mapping_ovrd.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_domain_mapping_ovrd.sql
index 343adf0bc..d2e08c899 100644
--- a/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_domain_mapping_ovrd.sql
+++ b/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_domain_mapping_ovrd.sql
@@ -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();
diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_mapping_override.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_mapping_override.sql
index 92ccd468c..6fb3bef8b 100644
--- a/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_mapping_override.sql
+++ b/internal/db/sqltest/tests/credential/vault/credential_vault_library_username_password_mapping_override.sql
@@ -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;
diff --git a/internal/db/sqltest/tests/history/dynamic_credentials.sql b/internal/db/sqltest/tests/history/dynamic_credentials.sql
index 4705ddbf2..f49bd5ad8 100644
--- a/internal/db/sqltest/tests/history/dynamic_credentials.sql
+++ b/internal/db/sqltest/tests/history/dynamic_credentials.sql
@@ -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'
);
diff --git a/internal/db/sqltest/tests/pagination/credential_library.sql b/internal/db/sqltest/tests/pagination/credential_library.sql
index 8d1f4a277..99e662d02 100644
--- a/internal/db/sqltest/tests/pagination/credential_library.sql
+++ b/internal/db/sqltest/tests/pagination/credential_library.sql
@@ -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();
diff --git a/internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql b/internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql
index dbe090e8d..286040df5 100644
--- a/internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql
+++ b/internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql
@@ -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;
diff --git a/internal/db/sqltest/tests/wh/credential_dimension/session_multiple_sessions.sql b/internal/db/sqltest/tests/wh/credential_dimension/session_multiple_sessions.sql
index a13d3d8d6..71057a0ef 100644
--- a/internal/db/sqltest/tests/wh/credential_dimension/session_multiple_sessions.sql
+++ b/internal/db/sqltest/tests/wh/credential_dimension/session_multiple_sessions.sql
@@ -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
diff --git a/internal/db/sqltest/tests/wh/credential_dimension/three_credentials_one_change.sql b/internal/db/sqltest/tests/wh/credential_dimension/three_credentials_one_change.sql
index d7d436cda..26e912b3c 100644
--- a/internal/db/sqltest/tests/wh/credential_dimension/three_credentials_one_change.sql
+++ b/internal/db/sqltest/tests/wh/credential_dimension/three_credentials_one_change.sql
@@ -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)
diff --git a/internal/proto/controller/api/resources/credentiallibraries/v1/credential_library.proto b/internal/proto/controller/api/resources/credentiallibraries/v1/credential_library.proto
index 565723d3b..e75ec5bcb 100644
--- a/internal/proto/controller/api/resources/credentiallibraries/v1/credential_library.proto
+++ b/internal/proto/controller/api/resources/credentiallibraries/v1/credential_library.proto
@@ -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"`
+}
diff --git a/internal/proto/controller/storage/credential/vault/store/v1/vault.proto b/internal/proto/controller/storage/credential/vault/store/v1/vault.proto
index a549c625a..25a3b02d9 100644
--- a/internal/proto/controller/storage/credential/vault/store/v1/vault.proto
+++ b/internal/proto/controller/storage/credential/vault/store/v1/vault.proto
@@ -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;
+}
diff --git a/internal/tests/cli/boundary/_credential_libraries.bash b/internal/tests/cli/boundary/_credential_libraries.bash
index a4ba27e68..534e6ec1c 100644
--- a/internal/tests/cli/boundary/_credential_libraries.bash
+++ b/internal/tests/cli/boundary/_credential_libraries.bash
@@ -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 $@
diff --git a/internal/tests/cli/boundary/credential_libraries.bats b/internal/tests/cli/boundary/credential_libraries.bats
index 33d086aac..47f9677f9 100644
--- a/internal/tests/cli/boundary/credential_libraries.bats
+++ b/internal/tests/cli/boundary/credential_libraries.bats
@@ -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
diff --git a/sdk/pbs/controller/api/resources/credentiallibraries/credential_library.pb.go b/sdk/pbs/controller/api/resources/credentiallibraries/credential_library.pb.go
index aa2402579..ba98a316b 100644
--- a/sdk/pbs/controller/api/resources/credentiallibraries/credential_library.pb.go
+++ b/sdk/pbs/controller/api/resources/credentiallibraries/credential_library.pb.go
@@ -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,
},
diff --git a/testing/dbtest/docker/Makefile b/testing/dbtest/docker/Makefile
index 4b5a3aed1..88f0ae72b 100644
--- a/testing/dbtest/docker/Makefile
+++ b/testing/dbtest/docker/Makefile
@@ -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
diff --git a/testing/dbtest/docker/init-db.sh b/testing/dbtest/docker/init-db.sh
index 75bc8015b..6ad1abff5 100755
--- a/testing/dbtest/docker/init-db.sh
+++ b/testing/dbtest/docker/init-db.sh
@@ -62,3 +62,11 @@ done
psql -v "ON_ERROR_STOP=1" --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -q <