feat(credential/vault): Implement LDAP Credential Library
This commit is contained in:
1 parent
ebf0450b03
commit
9e08eafcff
17 files changed
+2375
-105
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ package vault
|
||||
import "testing"
|
||||
|
||||
var (
|
||||
newVaultServer func(t testing.TB, opt ...TestOption) *TestVaultServer = skipNewServer
|
||||
mountDatabase func(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestDatabase = skipMountDatabase
|
||||
newVaultServer func(t testing.TB, opt ...TestOption) *TestVaultServer = skipNewServer
|
||||
mountLdapServer func(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestLdapServer = skipMountLdapServer
|
||||
mountDatabase func(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestDatabase = skipMountDatabase
|
||||
)
|
||||
|
||||
func skipNewServer(t testing.TB, opt ...TestOption) *TestVaultServer {
|
||||
@@ -19,3 +20,8 @@ func skipMountDatabase(t testing.TB, v *TestVaultServer, opt ...TestOption) *Tes
|
||||
t.Skip("docker not available")
|
||||
return nil
|
||||
}
|
||||
|
||||
func skipMountLdapServer(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestLdapServer {
|
||||
t.Skip("docker not available")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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.
|
||||
type LdapCredentialLibrary struct {
|
||||
*store.LdapCredentialLibrary
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// NewLdapCredentialLibrary creates a new in memory LdapCredentialLibrary
|
||||
// for a Vault backend at vaultPath assigned to storeId.
|
||||
// Name and description 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"
|
||||
}
|
||||
@@ -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"
|
||||
@@ -684,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,
|
||||
@@ -1110,3 +1147,232 @@ func (lib *sshCertIssuingCredentialLibrary) retrieveCredential(ctx context.Conte
|
||||
certificate: []byte(cert),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ldapIssuingCredentialLibrary struct {
|
||||
PublicId string
|
||||
StoreId string
|
||||
Name string
|
||||
Description string
|
||||
CreateTime *timestamp.Timestamp
|
||||
UpdateTime *timestamp.Timestamp
|
||||
Version uint32
|
||||
VaultPath string
|
||||
CredType string
|
||||
ProjectId string
|
||||
VaultAddress string
|
||||
Namespace string
|
||||
CaCert []byte
|
||||
TlsServerName string
|
||||
TlsSkipVerify bool
|
||||
WorkerFilter string
|
||||
Token TokenSecret
|
||||
CtToken []byte
|
||||
TokenHmac []byte
|
||||
TokenKeyId string
|
||||
ClientCert []byte
|
||||
ClientKey KeySecret
|
||||
CtClientKey []byte
|
||||
ClientKeyId string
|
||||
Username string
|
||||
KeyType string
|
||||
KeyBits int
|
||||
KeyId string
|
||||
Ttl string
|
||||
CriticalOptions []byte
|
||||
Extensions []byte
|
||||
Purpose credential.Purpose
|
||||
AdditionalValidPrincipals string
|
||||
}
|
||||
|
||||
// GetPublicId returns the credential library's id.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetPublicId() string { return lib.PublicId }
|
||||
|
||||
// GetStoreId returns the credential library's credential store id.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetStoreId() string { return lib.StoreId }
|
||||
|
||||
// GetName returns the credential library's name.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetName() string { return lib.Name }
|
||||
|
||||
// GetDescription returns the credential library's description.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetDescription() string { return lib.Description }
|
||||
|
||||
// GetVersion returns the credential library's version.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetVersion() uint32 { return lib.Version }
|
||||
|
||||
// GetPurpose returns the credential library's purpose.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetPurpose() credential.Purpose { return lib.Purpose }
|
||||
|
||||
// GetCreateTime returns the credential library's create time.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return lib.CreateTime }
|
||||
|
||||
// GetUpdateTime returns the credential library's update time.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return lib.UpdateTime }
|
||||
|
||||
// CredentialType returns the credential library's issuing credential type.
|
||||
func (lib *ldapIssuingCredentialLibrary) CredentialType() globals.CredentialType {
|
||||
switch ct := lib.CredType; ct {
|
||||
case "":
|
||||
return globals.UnspecifiedCredentialType
|
||||
default:
|
||||
return globals.CredentialType(ct)
|
||||
}
|
||||
}
|
||||
|
||||
// GetResourceType returns the credential library's resource type.
|
||||
func (lib *ldapIssuingCredentialLibrary) GetResourceType() resource.Type {
|
||||
return resource.CredentialLibrary
|
||||
}
|
||||
|
||||
// client produces a Vault API client using this credential library's data.
|
||||
func (lib *ldapIssuingCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
|
||||
const op = "vault.(genericIssuingCredentialLibrary).client"
|
||||
clientConfig := &clientConfig{
|
||||
Addr: lib.VaultAddress,
|
||||
Token: lib.Token,
|
||||
CaCert: lib.CaCert,
|
||||
TlsServerName: lib.TlsServerName,
|
||||
TlsSkipVerify: lib.TlsSkipVerify,
|
||||
Namespace: lib.Namespace,
|
||||
}
|
||||
|
||||
if lib.ClientKey != nil {
|
||||
clientConfig.ClientCert = lib.ClientCert
|
||||
clientConfig.ClientKey = lib.ClientKey
|
||||
}
|
||||
|
||||
client, err := vaultClientFactoryFn(ctx, clientConfig, WithWorkerFilter(lib.WorkerFilter))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create vault client"))
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
var vaultLdapPathRegexp = regexp.MustCompile(`^.+\/(static-cred|creds).+$`)
|
||||
|
||||
// retrieveCredential retrieves a dynamic LDAP credential from Vault for a
|
||||
// specific session and connection.
|
||||
func (lib *ldapIssuingCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, _ ...credential.Option) (dynamicCred, error) {
|
||||
credId, err := newCredentialId(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
client, err := lib.client(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
var secret *vault.Secret
|
||||
|
||||
secret, err = client.get(ctx, lib.VaultPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
|
||||
}
|
||||
|
||||
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
|
||||
cred, err := newCredential(ctx, lib.GetPublicId(), secret.LeaseID, lib.TokenHmac, leaseDuration)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
cred.PublicId = credId
|
||||
cred.IsRenewable = secret.Renewable
|
||||
|
||||
upd := &usrPassDomainCred{
|
||||
baseCred: &baseCred{
|
||||
Credential: cred,
|
||||
lib: lib,
|
||||
secretData: secret.Data,
|
||||
},
|
||||
}
|
||||
|
||||
username, ok := secret.Data["username"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a username or response was not in the expected format")
|
||||
}
|
||||
upd.username = username
|
||||
|
||||
pwData, ok := secret.Data["password"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a password or response was not in the expected format")
|
||||
}
|
||||
upd.password = credential.Password(pwData)
|
||||
|
||||
matches := vaultLdapPathRegexp.FindStringSubmatch(lib.VaultPath)
|
||||
if len(matches) < 2 { // [0] is the vault path, [1] is static-cred or creds, if it exists.
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "vault path was not in an expected format. expected path containing \"static-cred\" or \"creds\"")
|
||||
}
|
||||
switch matches[1] {
|
||||
case "static-cred":
|
||||
dn, ok := secret.Data["dn"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a dn or response was not in the expected format")
|
||||
}
|
||||
domain, err := extractDomainFromDn(dn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
upd.domain = domain
|
||||
case "creds":
|
||||
var dns []string
|
||||
dis, ok := secret.Data["distinguished_names"].([]any)
|
||||
if ok {
|
||||
for _, value := range dis {
|
||||
if v, ok := value.(string); ok {
|
||||
dns = append(dns, v)
|
||||
} else {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret returned distinguished_names with non-string member")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain distinguished_names or response was not in the expected format")
|
||||
}
|
||||
if len(dns) == 0 {
|
||||
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret returned empty distinguished_names")
|
||||
}
|
||||
|
||||
// In the Vault LDAP secrets engine, no deduplication occurs and order
|
||||
// is maintained from the LDIF statements, so the assumption here is
|
||||
// that the first DN should correspond to the user's creation in the
|
||||
// creation LDIF, which should correspond to the correct domain.
|
||||
// https://developer.hashicorp.com/vault/docs/secrets/ldap#dynamic-credentials
|
||||
domain, err := extractDomainFromDn(dns[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
upd.domain = domain
|
||||
}
|
||||
|
||||
return upd, nil
|
||||
}
|
||||
|
||||
// extractDomainFromDn composes a domain name from all the "dc" attributes in an
|
||||
// LDAP DN.
|
||||
func extractDomainFromDn(dnStr string) (string, error) {
|
||||
if dnStr == "" {
|
||||
return "", fmt.Errorf("empty DN")
|
||||
}
|
||||
|
||||
dn, err := ldapv3.ParseDN(dnStr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse DN: %w", err)
|
||||
}
|
||||
|
||||
var dcs []string
|
||||
for _, rdn := range dn.RDNs {
|
||||
for _, attr := range rdn.Attributes {
|
||||
// "0.9.2342.19200300.100.1.25" is the OID for the "dc" attribute
|
||||
// and it is a valid representation in a DN string.
|
||||
// (https://docs.ldap.com/specs/rfc4519.txt)
|
||||
if strings.EqualFold(attr.Type, "dc") || strings.EqualFold(attr.Type, "0.9.2342.19200300.100.1.25") {
|
||||
dcs = append(dcs, attr.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(dcs) == 0 {
|
||||
return "", fmt.Errorf("no domain component (dc) attribute found in dn")
|
||||
}
|
||||
|
||||
return strings.Join(dcs, "."), nil
|
||||
}
|
||||
@@ -1879,6 +1879,136 @@ func TestBaseToSshPriKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ldapCertIssuingCredentialLibrary_retrieveCredential(t *testing.T) {
|
||||
// Create Vault server
|
||||
v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS), WithVaultVersion("1.20.0"), WithDockerNetwork(true))
|
||||
require.NotNil(t, v)
|
||||
|
||||
vc := v.client(t).cl
|
||||
mounts, err := vc.Sys().ListMounts()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, mounts)
|
||||
|
||||
// Create OpenLDAP server and mount Vault LDAP secrets engine.
|
||||
_ = MountLdapServer(t, v)
|
||||
|
||||
// Create and setup Boundary DB
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, repo)
|
||||
|
||||
sec, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "ldap"}), WithTokenPeriod(time.Hour))
|
||||
require.NotNil(t, sec)
|
||||
require.NotEmpty(t, token)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), v.Addr, token, sec.Auth.Accessor)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vaultPath string
|
||||
expStaticUsername string
|
||||
expDomain string
|
||||
expDynamicCred bool
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "doesntExistStaticCred",
|
||||
vaultPath: "ldap/static-cred/doesntexist",
|
||||
expErr: true,
|
||||
expErrMsg: "unknown role:",
|
||||
},
|
||||
{
|
||||
name: "doesntExistDynamicCred",
|
||||
vaultPath: "ldap/creds/doesntexist",
|
||||
expErr: true,
|
||||
expErrMsg: "vault secret is empty",
|
||||
},
|
||||
{
|
||||
name: "staticCred",
|
||||
vaultPath: "ldap/static-cred/einstein",
|
||||
expStaticUsername: "einstein",
|
||||
expDomain: "example.org",
|
||||
},
|
||||
{
|
||||
name: "staticCredHierarchical",
|
||||
vaultPath: "ldap/static-cred/myorg/myproject/newton",
|
||||
expStaticUsername: "newton",
|
||||
expDomain: "example.org",
|
||||
},
|
||||
{
|
||||
name: "dynamicCred",
|
||||
vaultPath: "ldap/creds/scientists",
|
||||
expDynamicCred: true,
|
||||
expDomain: "example.org",
|
||||
},
|
||||
{
|
||||
name: "dynamicCredHierarchical",
|
||||
vaultPath: "ldap/creds/myorg/myproject/scientists",
|
||||
expDynamicCred: true,
|
||||
expDomain: "example.org",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
lib, err := NewLdapCredentialLibrary(cs.GetPublicId(), tt.vaultPath)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, lib)
|
||||
lib.PublicId, err = newLdapCredentialLibraryId(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = rw.DoTx(t.Context(), db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
return iw.Create(t.Context(), lib)
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
libs, err := repo.getIssueCredLibraries(t.Context(), []credential.Request{{
|
||||
SourceId: lib.GetPublicId(),
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, libs, 1)
|
||||
|
||||
cred, err := libs[0].retrieveCredential(t.Context(), "TestLdapRetrieveCredential")
|
||||
if tt.expErr {
|
||||
require.ErrorContains(t, err, tt.expErrMsg)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.IsType(t, &usrPassDomainCred{}, cred)
|
||||
credUpd := cred.(*usrPassDomainCred)
|
||||
require.NotEmpty(t, credUpd.baseCred)
|
||||
require.NotEmpty(t, credUpd.getCredential())
|
||||
require.EqualValues(t, libs[0], credUpd.lib)
|
||||
require.NotEmpty(t, credUpd.secretData)
|
||||
require.Contains(t, credUpd.secretData, "username")
|
||||
require.Contains(t, credUpd.secretData, "password")
|
||||
|
||||
require.EqualValues(t, credUpd.secretData["password"], credUpd.Password())
|
||||
require.EqualValues(t, tt.expDomain, credUpd.Domain())
|
||||
if !tt.expDynamicCred {
|
||||
require.EqualValues(t, credUpd.secretData["username"], credUpd.Username())
|
||||
require.EqualValues(t, tt.expStaticUsername, credUpd.Username())
|
||||
require.False(t, credUpd.GetIsRenewable())
|
||||
} else {
|
||||
require.EqualValues(t, credUpd.secretData["username"], credUpd.Username())
|
||||
require.Contains(t, credUpd.Username(), "b_token-TestRepository-ldapCertIssuingCredentialLibrary-retrieveCredential_")
|
||||
require.True(t, credUpd.GetIsRenewable())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_sshCertIssuingCredentialLibrary_retrieveCredential(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -2093,3 +2223,83 @@ func TestRepository_sshCertIssuingCredentialLibrary_retrieveCredential(t *testin
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDomainFromDn(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inDn string
|
||||
expDomain string
|
||||
expErr bool
|
||||
expErrMsgContains string
|
||||
}{
|
||||
{
|
||||
name: "emptyString",
|
||||
inDn: "",
|
||||
expErr: true,
|
||||
expErrMsgContains: "empty DN",
|
||||
},
|
||||
{
|
||||
name: "notADn",
|
||||
inDn: "a string that isn't really a DN",
|
||||
expErr: true,
|
||||
expErrMsgContains: "failed to parse DN:",
|
||||
},
|
||||
{
|
||||
name: "validDn",
|
||||
inDn: "dc=domain,dc=tld",
|
||||
expDomain: "domain.tld",
|
||||
},
|
||||
{
|
||||
name: "validDnSpaces",
|
||||
inDn: "dc = domain , dc = tld",
|
||||
expDomain: "domain.tld",
|
||||
},
|
||||
{
|
||||
name: "validDnCaps",
|
||||
inDn: "DC=DOMAIN,DC=TLD",
|
||||
expDomain: "DOMAIN.TLD",
|
||||
},
|
||||
{
|
||||
name: "validDnOid",
|
||||
inDn: "0.9.2342.19200300.100.1.25=Domain,0.9.2342.19200300.100.1.25=Tld",
|
||||
expDomain: "Domain.Tld",
|
||||
},
|
||||
{
|
||||
name: "validDnOidMixed",
|
||||
inDn: "dc=sub1,0.9.2342.19200300.100.1.25=Domain,dc=Tld",
|
||||
expDomain: "sub1.Domain.Tld",
|
||||
},
|
||||
{
|
||||
name: "validDnWithCn",
|
||||
inDn: "CN=My User,CN=admin,DC=mycorp,DC=domain,DC=TLD",
|
||||
expDomain: "mycorp.domain.TLD",
|
||||
},
|
||||
{
|
||||
name: "validDnWithEscapeChars",
|
||||
inDn: `CN=mycn,OU=HashiCorp\, an IBM Company,DC=domain,DC=TLD`,
|
||||
expDomain: "domain.TLD",
|
||||
},
|
||||
{
|
||||
name: "validDnWithEscapeChars2",
|
||||
inDn: `CN=Before\0DAfter,OU=HashiCorp,DC=domain,DC=TLD`,
|
||||
expDomain: "domain.TLD",
|
||||
},
|
||||
{
|
||||
name: "validDnWithSubdomains",
|
||||
inDn: `dc=foo,dc=bAR,dc=BAZ,dc=domain,dc=Tld`,
|
||||
expDomain: "foo.bAR.BAZ.domain.Tld",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
domain, err := extractDomainFromDn(tt.inDn)
|
||||
if tt.expErr {
|
||||
require.ErrorContains(t, err, tt.expErrMsgContains)
|
||||
require.Empty(t, domain)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expDomain, domain)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -195,7 +195,8 @@ select sum(reltuples::bigint) as estimate
|
||||
from pg_class
|
||||
where oid in (
|
||||
'credential_vault_generic_library'::regclass,
|
||||
'credential_vault_ssh_cert_library'::regclass
|
||||
'credential_vault_ssh_cert_library'::regclass,
|
||||
'credential_vault_ldap_library'::regclass
|
||||
)
|
||||
`
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/go-dbw"
|
||||
)
|
||||
|
||||
// CreateLdapCredentialLibrary creates a new Vault LDAP credential library. It
|
||||
// inserts the input domain object into the repository and returns the inserted
|
||||
// domain object. No options are supported.
|
||||
//
|
||||
// The input domain object is not mutated, must contain a valid credential store
|
||||
// id and must not contain a public id.
|
||||
func (r *Repository) CreateLdapCredentialLibrary(ctx context.Context, projectId string, l *LdapCredentialLibrary, _ ...Option) (*LdapCredentialLibrary, error) {
|
||||
const op = "vault.(Repository).CreateLdapCredentialLibrary"
|
||||
if projectId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no project id")
|
||||
}
|
||||
if l == nil || l.LdapCredentialLibrary == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil input domain object")
|
||||
}
|
||||
if l.PublicId != "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty")
|
||||
}
|
||||
if l.StoreId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no store id")
|
||||
}
|
||||
if l.VaultPath == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no vault path")
|
||||
}
|
||||
l = l.clone()
|
||||
|
||||
if l.GetCredentialType() == "" {
|
||||
l.LdapCredentialLibrary.CredentialType = string(globals.UsernamePasswordDomainCredentialType)
|
||||
}
|
||||
if l.GetCredentialType() != string(globals.UsernamePasswordDomainCredentialType) {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid credential type")
|
||||
}
|
||||
|
||||
id, err := newLdapCredentialLibraryId(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
l.setId(id)
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
var insertedLdapCredLib *LdapCredentialLibrary
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
insertedLdapCredLib = l.clone()
|
||||
err = w.Create(ctx, insertedLdapCredLib,
|
||||
db.WithOplog(oplogWrapper, insertedLdapCredLib.oplog(oplog.OpType_OP_TYPE_CREATE)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.IsUniqueError(err) {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("in credential store: %s: name %s already exists", l.StoreId, l.Name))
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("credential store: %s", l.StoreId))
|
||||
}
|
||||
|
||||
return insertedLdapCredLib, nil
|
||||
}
|
||||
|
||||
// UpdateLdapCredentialLibrary updates an existing Vault LDAP credential library
|
||||
// in the repository. It returns a domain object containing the updated state
|
||||
// and the number of updated records. No options are supported.
|
||||
//
|
||||
// The input domain object is not mutated and must contain a valid public id.
|
||||
func (r *Repository) UpdateLdapCredentialLibrary(ctx context.Context, projectId string, l *LdapCredentialLibrary, version uint32, fieldMaskPaths []string, _ ...Option) (*LdapCredentialLibrary, int, error) {
|
||||
const op = "vault.(Repository).UpdateLdapCredentialLibrary"
|
||||
if projectId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no project id")
|
||||
}
|
||||
if version == 0 {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
|
||||
}
|
||||
if l == nil || l.LdapCredentialLibrary == nil {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "nil domain object")
|
||||
}
|
||||
if l.PublicId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
|
||||
}
|
||||
|
||||
for _, f := range fieldMaskPaths {
|
||||
switch {
|
||||
case strings.EqualFold(nameField, f):
|
||||
case strings.EqualFold(descriptionField, f):
|
||||
case strings.EqualFold(vaultPathField, f):
|
||||
default:
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("%q field mask path is unsupported", f))
|
||||
}
|
||||
}
|
||||
|
||||
dbMask, nullFields := dbw.BuildUpdatePaths(
|
||||
map[string]any{
|
||||
nameField: l.Name,
|
||||
descriptionField: l.Description,
|
||||
vaultPathField: l.VaultPath,
|
||||
}, fieldMaskPaths, nil,
|
||||
)
|
||||
if len(dbMask) == 0 && len(nullFields) == 0 {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
|
||||
}
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
var updatedCredLib *LdapCredentialLibrary
|
||||
var rowsUpdated int
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(r db.Reader, w db.Writer) error {
|
||||
updated, err := w.Update(ctx, l.clone(), dbMask, nullFields,
|
||||
db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_UPDATE)),
|
||||
db.WithVersion(&version),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update credential library"))
|
||||
}
|
||||
if updated > 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 credential library would have been updated")
|
||||
}
|
||||
rowsUpdated = updated
|
||||
|
||||
updatedCredLib = allocLdapCredentialLibrary()
|
||||
updatedCredLib.PublicId = l.GetPublicId()
|
||||
err = r.LookupByPublicId(ctx, updatedCredLib)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve updated credential library"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.IsUniqueError(err) {
|
||||
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("name %s already exists: %s", l.Name, l.PublicId))
|
||||
}
|
||||
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("failed to update credential library %s", l.PublicId))
|
||||
}
|
||||
|
||||
return updatedCredLib, rowsUpdated, nil
|
||||
}
|
||||
|
||||
// LookupLdapCredentialLibrary looks up an existing Vault LDAP credential
|
||||
// library in the repository. It returns the domain object for the given public
|
||||
// id, if it exists. Returns nil, nil if not found. No options are supported.
|
||||
func (r *Repository) LookupLdapCredentialLibrary(ctx context.Context, publicId string, _ ...Option) (*LdapCredentialLibrary, error) {
|
||||
const op = "vault.(Repository).LookupLdapCredentialLibrary"
|
||||
if publicId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
|
||||
}
|
||||
|
||||
l := allocLdapCredentialLibrary()
|
||||
l.PublicId = publicId
|
||||
err := r.reader.LookupByPublicId(ctx, l)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("lookup failed for id %s", publicId))
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// DeleteLdapCredentialLibrary deletes the Vault LDAP credential library with
|
||||
// the given public id from the repository. Returns the number of records
|
||||
// deleted. No options are supported.
|
||||
func (r *Repository) DeleteLdapCredentialLibrary(ctx context.Context, projectId string, publicId string, _ ...Option) (int, error) {
|
||||
const op = "vault.(Repository).DeleteLdapCredentialLibrary"
|
||||
if publicId == "" {
|
||||
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id")
|
||||
}
|
||||
if projectId == "" {
|
||||
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no project id")
|
||||
}
|
||||
|
||||
l := allocLdapCredentialLibrary()
|
||||
l.PublicId = publicId
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
var rowsDeleted int
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) (err error) {
|
||||
rowsDeleted, err = w.Delete(ctx, l, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_DELETE)))
|
||||
if err == nil && rowsDeleted > 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 credential library would have been deleted")
|
||||
}
|
||||
return err
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("delete failed for id %s", l.PublicId))
|
||||
}
|
||||
|
||||
return rowsDeleted, nil
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package vault
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/scheduler"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func TestCreateLdapCredentialLibrary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inProjectId string
|
||||
inDomainObj *LdapCredentialLibrary
|
||||
expOut *LdapCredentialLibrary
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "noProjectId",
|
||||
inProjectId: "",
|
||||
inDomainObj: nil,
|
||||
expErr: true,
|
||||
expErrMsg: "no project id",
|
||||
},
|
||||
{
|
||||
name: "nilDomainObject",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: nil,
|
||||
expErr: true,
|
||||
expErrMsg: "nil input domain object",
|
||||
},
|
||||
{
|
||||
name: "nilStoreObject",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{LdapCredentialLibrary: nil},
|
||||
expErr: true,
|
||||
expErrMsg: "nil input domain object",
|
||||
},
|
||||
{
|
||||
name: "emptyObject",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{LdapCredentialLibrary: &store.LdapCredentialLibrary{}},
|
||||
expErr: true,
|
||||
expErrMsg: "no store id",
|
||||
},
|
||||
{
|
||||
name: "withPublicId",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
PublicId: "cvllt_3o2iuhOpZ",
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "withPublicId",
|
||||
Description: "withPublicId Subtest",
|
||||
VaultPath: "ldap/creds/dynamic1",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "public id not empty",
|
||||
},
|
||||
{
|
||||
name: "noStoreId",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: "",
|
||||
Name: "noStoreId",
|
||||
Description: "noStoreId Subtest",
|
||||
VaultPath: "ldap/creds/dynamic1",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "no store id",
|
||||
},
|
||||
{
|
||||
name: "noVaultPath",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "noVaultPath",
|
||||
Description: "noVaultPath Subtest",
|
||||
VaultPath: "",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "no vault path",
|
||||
},
|
||||
{
|
||||
name: "invalidCredentialType",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "invalidCredentialType",
|
||||
Description: "invalidCredentialType Subtest",
|
||||
VaultPath: "ldap/creds/dynamic1",
|
||||
CredentialType: "cred_type_that_doesnt_exist",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "invalid credential type",
|
||||
},
|
||||
{
|
||||
name: "invalidVaultPath1",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "invalidVaultPath1",
|
||||
Description: "invalidVaultPath1 Subtest",
|
||||
VaultPath: "ldap/bad/some/test/path",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
|
||||
},
|
||||
{
|
||||
name: "invalidVaultPath2",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "invalidVaultPath2",
|
||||
Description: "invalidVaultPath2 Subtest",
|
||||
VaultPath: "ldap/static-credbad/some/test/path",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
|
||||
},
|
||||
{
|
||||
name: "invalidVaultPath3",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "invalidVaultPath3",
|
||||
Description: "invalidVaultPath3 Subtest",
|
||||
VaultPath: "ldap/badstatic-cred/some/test/path",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
|
||||
},
|
||||
{
|
||||
name: "invalidVaultPath4",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "invalidVaultPath4",
|
||||
Description: "invalidVaultPath4 Subtest",
|
||||
VaultPath: "ldap/credsbad/some/test/path",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
|
||||
},
|
||||
{
|
||||
name: "invalidVaultPath5",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "invalidVaultPath5",
|
||||
Description: "invalidVaultPath5 Subtest",
|
||||
VaultPath: "ldap/badcreds/some/test/path",
|
||||
},
|
||||
},
|
||||
expErr: true,
|
||||
expErrMsg: "vault_path_must_have_staticcred_or_creds constraint failed",
|
||||
},
|
||||
{
|
||||
name: "validStaticCred1",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "validStaticCred1",
|
||||
Description: "validStaticCred1 Subtest",
|
||||
VaultPath: "ldap/static-cred/some/test/path",
|
||||
},
|
||||
},
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "validStaticCred1",
|
||||
Description: "validStaticCred1 Subtest",
|
||||
VaultPath: "ldap/static-cred/some/test/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validStaticCred2",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "validStaticCred2",
|
||||
Description: "validStaticCred2 Subtest",
|
||||
VaultPath: "/ldap/static-cred/path",
|
||||
},
|
||||
},
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "validStaticCred2",
|
||||
Description: "validStaticCred2 Subtest",
|
||||
VaultPath: "/ldap/static-cred/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validDynamicCreds1",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "validDynamicCreds1",
|
||||
Description: "validDynamicCreds1 Subtest",
|
||||
VaultPath: "ldap/creds/some/test/path",
|
||||
},
|
||||
},
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "validDynamicCreds1",
|
||||
Description: "validDynamicCreds1 Subtest",
|
||||
VaultPath: "ldap/creds/some/test/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validDynamicCreds2",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inDomainObj: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "validDynamicCreds2",
|
||||
Description: "validDynamicCreds2 Subtest",
|
||||
VaultPath: "/ldap/creds/path",
|
||||
},
|
||||
},
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "validDynamicCreds2",
|
||||
Description: "validDynamicCreds2 Subtest",
|
||||
VaultPath: "/ldap/creds/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, repo)
|
||||
|
||||
ldapCredLib, err := repo.CreateLdapCredentialLibrary(t.Context(), tt.inProjectId, tt.inDomainObj)
|
||||
if tt.expErr {
|
||||
require.ErrorContains(t, err, tt.expErrMsg)
|
||||
require.Nil(t, ldapCredLib)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ldapCredLib)
|
||||
|
||||
require.Contains(t, ldapCredLib.GetPublicId(), globals.VaultLdapCredentialLibraryPrefix)
|
||||
require.NotEmpty(t, ldapCredLib.GetCreateTime())
|
||||
require.NotEmpty(t, ldapCredLib.GetUpdateTime())
|
||||
require.EqualValues(t, cs.GetPublicId(), ldapCredLib.GetStoreId())
|
||||
require.EqualValues(t, 1, ldapCredLib.GetVersion())
|
||||
require.EqualValues(t, string(globals.UsernamePasswordDomainCredentialType), ldapCredLib.GetCredentialType())
|
||||
require.EqualValues(t, tt.expOut.GetName(), ldapCredLib.GetName())
|
||||
require.EqualValues(t, tt.expOut.GetDescription(), ldapCredLib.GetDescription())
|
||||
require.EqualValues(t, tt.expOut.GetVaultPath(), ldapCredLib.GetVaultPath())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateLdapCredentialLibrary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
|
||||
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, repo)
|
||||
|
||||
t.Run("nilDomainObject", func(t *testing.T) {
|
||||
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), nil, 1, []string{})
|
||||
require.ErrorContains(t, err, "nil domain object")
|
||||
require.EqualValues(t, 0, updated)
|
||||
require.Nil(t, l)
|
||||
})
|
||||
|
||||
t.Run("nilStoreObject", func(t *testing.T) {
|
||||
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(),
|
||||
&LdapCredentialLibrary{LdapCredentialLibrary: nil}, 1, []string{})
|
||||
require.ErrorContains(t, err, "nil domain object")
|
||||
require.EqualValues(t, 0, updated)
|
||||
require.Nil(t, l)
|
||||
})
|
||||
|
||||
t.Run("emptyDomainObject", func(t *testing.T) {
|
||||
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(),
|
||||
&LdapCredentialLibrary{LdapCredentialLibrary: &store.LdapCredentialLibrary{}},
|
||||
1, []string{},
|
||||
)
|
||||
require.ErrorContains(t, err, "missing public id")
|
||||
require.EqualValues(t, 0, updated)
|
||||
require.Nil(t, l)
|
||||
})
|
||||
|
||||
t.Run("credLibDoesntExist", func(t *testing.T) {
|
||||
l, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(), proj.GetPublicId(),
|
||||
&LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{PublicId: "doesnt_exist"},
|
||||
}, 1, []string{"name", "description"},
|
||||
)
|
||||
require.ErrorContains(t, err, "record not found, search issue")
|
||||
require.EqualValues(t, 0, updated)
|
||||
require.Nil(t, l)
|
||||
})
|
||||
|
||||
updateNameFn := func(name string) func(*LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
lcl.Name = name
|
||||
return lcl
|
||||
}
|
||||
}
|
||||
updateDescriptionFn := func(description string) func(*LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
lcl.Description = description
|
||||
return lcl
|
||||
}
|
||||
}
|
||||
updateVaultPathFn := func(vaultPath string) func(*LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
lcl.VaultPath = vaultPath
|
||||
return lcl
|
||||
}
|
||||
}
|
||||
updateStoreIdFn := func() func(*LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
return func(lcl *LdapCredentialLibrary) *LdapCredentialLibrary {
|
||||
lcl.StoreId = "this_is_immutable"
|
||||
return lcl
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inProjectId string
|
||||
inVersion uint32
|
||||
inFieldMask []string
|
||||
changeFns []func(*LdapCredentialLibrary) *LdapCredentialLibrary
|
||||
expUpdate bool
|
||||
expOut *LdapCredentialLibrary
|
||||
expErr bool
|
||||
expErrStr string
|
||||
}{
|
||||
{
|
||||
name: "noProjectId",
|
||||
inProjectId: "",
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"somepath"},
|
||||
expErr: true,
|
||||
expErrStr: "no project id",
|
||||
},
|
||||
{
|
||||
name: "noVersion",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 0,
|
||||
inFieldMask: []string{"somepath"},
|
||||
expErr: true,
|
||||
expErrStr: "missing version",
|
||||
},
|
||||
{
|
||||
name: "missingFieldMask",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{},
|
||||
expErr: true,
|
||||
expErrStr: "missing field mask",
|
||||
},
|
||||
{
|
||||
name: "unsupportedFieldInFieldMask",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"name", "storeId"},
|
||||
expErr: true,
|
||||
expErrStr: `"storeId" field mask path is unsupported`,
|
||||
},
|
||||
{
|
||||
name: "updateToInvalidVaultPath1",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"vaultPath"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateVaultPathFn("ldap/credbad/new"),
|
||||
},
|
||||
expErr: true,
|
||||
expErrStr: "vault_path_must_have_staticcred_or_creds constraint failed",
|
||||
},
|
||||
{
|
||||
name: "updateToInvalidVaultPath2",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"vaultPath"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateVaultPathFn("ldap/static-credbad/new"),
|
||||
},
|
||||
expErr: true,
|
||||
expErrStr: "vault_path_must_have_staticcred_or_creds constraint failed",
|
||||
},
|
||||
{
|
||||
name: "updateAndFieldMaskMismatch",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"name"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateDescriptionFn("updateAndFieldMaskMismatch Updated Description"),
|
||||
},
|
||||
expUpdate: false,
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "TestUpdateLdapCredentialLibrary/updateAndFieldMaskMismatch Original Name",
|
||||
Description: "TestUpdateLdapCredentialLibrary/updateAndFieldMaskMismatch Original Description",
|
||||
VaultPath: "ldap/creds/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validUpdateName",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"name"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateNameFn("validUpdateName Subtest Updated Name"),
|
||||
},
|
||||
expUpdate: true,
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "validUpdateName Subtest Updated Name",
|
||||
Description: "TestUpdateLdapCredentialLibrary/validUpdateName Original Description",
|
||||
VaultPath: "ldap/creds/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validUpdateDescription",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"description"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateDescriptionFn("validUpdateDescription Subtest Updated Description"),
|
||||
},
|
||||
expUpdate: true,
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "TestUpdateLdapCredentialLibrary/validUpdateDescription Original Name",
|
||||
Description: "validUpdateDescription Subtest Updated Description",
|
||||
VaultPath: "ldap/creds/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validUpdateVaultPath",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"vaultPath"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateVaultPathFn("ldap/creds/updated/path/new"),
|
||||
},
|
||||
expUpdate: true,
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "TestUpdateLdapCredentialLibrary/validUpdateVaultPath Original Name",
|
||||
Description: "TestUpdateLdapCredentialLibrary/validUpdateVaultPath Original Description",
|
||||
VaultPath: "ldap/creds/updated/path/new",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validUpdateMultipleFields",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"name", "description", "vaultPath"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateNameFn("validUpdateMultipleFields Updated Name"),
|
||||
updateDescriptionFn("validUpdateMultipleFields Updated Description"),
|
||||
updateVaultPathFn("ldap/static-cred/updated/path/new"),
|
||||
},
|
||||
expUpdate: true,
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "validUpdateMultipleFields Updated Name",
|
||||
Description: "validUpdateMultipleFields Updated Description",
|
||||
VaultPath: "ldap/static-cred/updated/path/new",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validUpdateNameExtraChangesNotInFieldMask",
|
||||
inProjectId: proj.GetPublicId(),
|
||||
inVersion: 1,
|
||||
inFieldMask: []string{"name"},
|
||||
changeFns: []func(*LdapCredentialLibrary) *LdapCredentialLibrary{
|
||||
updateNameFn("validUpdateNameExtraChanges Updated Name"),
|
||||
updateDescriptionFn("validUpdateNameExtraChanges Updated Description"),
|
||||
updateVaultPathFn("ldap/creds/updated/path/new"),
|
||||
updateStoreIdFn(),
|
||||
},
|
||||
expUpdate: true,
|
||||
expOut: &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
Name: "validUpdateNameExtraChanges Updated Name",
|
||||
Description: "TestUpdateLdapCredentialLibrary/validUpdateNameExtraChangesNotInFieldMask Original Description",
|
||||
VaultPath: "ldap/creds/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
createdLib, err := repo.CreateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: t.Name() + " Original Name",
|
||||
Description: t.Name() + " Original Description",
|
||||
VaultPath: "ldap/creds/path",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdLib)
|
||||
|
||||
inDomainObj := createdLib.clone()
|
||||
for _, changeFn := range tt.changeFns {
|
||||
inDomainObj = changeFn(inDomainObj)
|
||||
}
|
||||
|
||||
updatedLdapLib, updated, err := repo.UpdateLdapCredentialLibrary(t.Context(),
|
||||
tt.inProjectId, inDomainObj, tt.inVersion, tt.inFieldMask,
|
||||
)
|
||||
if tt.expErr {
|
||||
require.ErrorContains(t, err, tt.expErrStr)
|
||||
require.Nil(t, updatedLdapLib)
|
||||
require.EqualValues(t, 0, updated)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, updatedLdapLib)
|
||||
require.NotNil(t, updatedLdapLib.LdapCredentialLibrary)
|
||||
|
||||
if tt.expUpdate {
|
||||
require.Greater(t, updatedLdapLib.GetUpdateTime().AsTime(), createdLib.GetUpdateTime().AsTime())
|
||||
require.EqualValues(t, createdLib.GetVersion()+1, updatedLdapLib.GetVersion())
|
||||
} else {
|
||||
require.EqualValues(t, createdLib.GetUpdateTime().AsTime(), updatedLdapLib.GetUpdateTime().AsTime())
|
||||
require.EqualValues(t, createdLib.GetVersion(), updatedLdapLib.GetVersion())
|
||||
}
|
||||
require.EqualValues(t, 1, updated)
|
||||
require.EqualValues(t, createdLib.GetPublicId(), updatedLdapLib.GetPublicId())
|
||||
require.EqualValues(t, createdLib.GetCreateTime().AsTime(), updatedLdapLib.GetCreateTime().AsTime())
|
||||
require.EqualValues(t, createdLib.GetStoreId(), updatedLdapLib.GetStoreId())
|
||||
require.EqualValues(t, createdLib.GetCredentialType(), updatedLdapLib.GetCredentialType())
|
||||
require.EqualValues(t, tt.expOut.GetName(), updatedLdapLib.GetName())
|
||||
require.EqualValues(t, tt.expOut.GetDescription(), updatedLdapLib.GetDescription())
|
||||
require.EqualValues(t, tt.expOut.GetVaultPath(), updatedLdapLib.GetVaultPath())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupLdapCredentialLibrary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
|
||||
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, repo)
|
||||
|
||||
t.Run("noPublicId", func(t *testing.T) {
|
||||
l, err := repo.LookupLdapCredentialLibrary(t.Context(), "")
|
||||
require.ErrorContains(t, err, "no public id")
|
||||
require.Nil(t, l)
|
||||
})
|
||||
|
||||
t.Run("credLibNotFound", func(t *testing.T) {
|
||||
l, err := repo.LookupLdapCredentialLibrary(t.Context(), "alibthat_doesnt_exist")
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, l)
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
createdLib, err := repo.CreateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "success",
|
||||
Description: "success Subtest",
|
||||
VaultPath: "ldap/creds/path",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdLib)
|
||||
|
||||
lookupLib, err := repo.LookupLdapCredentialLibrary(t.Context(), createdLib.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(createdLib, lookupLib, protocmp.Transform()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteLdapCredentialLibrary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
|
||||
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
repo, err := NewRepository(t.Context(), rw, rw, kms, sche)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, repo)
|
||||
|
||||
t.Run("noPublicId", func(t *testing.T) {
|
||||
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), proj.GetPublicId(), "")
|
||||
require.ErrorContains(t, err, "no public id")
|
||||
require.EqualValues(t, 0, deleted)
|
||||
})
|
||||
|
||||
t.Run("noProjectId", func(t *testing.T) {
|
||||
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), "", "cvllt_doesntexist")
|
||||
require.ErrorContains(t, err, "no project id")
|
||||
require.EqualValues(t, 0, deleted)
|
||||
})
|
||||
|
||||
t.Run("credLibDoesntExist", func(t *testing.T) {
|
||||
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), proj.GetPublicId(), "cvllt_doesntexist")
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, deleted)
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
createdLib, err := repo.CreateLdapCredentialLibrary(t.Context(), proj.GetPublicId(), &LdapCredentialLibrary{
|
||||
LdapCredentialLibrary: &store.LdapCredentialLibrary{
|
||||
StoreId: cs.GetPublicId(),
|
||||
Name: "success",
|
||||
Description: "success Subtest",
|
||||
VaultPath: "ldap/creds/path",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdLib)
|
||||
|
||||
lookupLib, err := repo.LookupLdapCredentialLibrary(t.Context(), createdLib.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(createdLib, lookupLib, protocmp.Transform()))
|
||||
|
||||
deleted, err := repo.DeleteLdapCredentialLibrary(t.Context(), proj.GetPublicId(), lookupLib.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, deleted)
|
||||
|
||||
lookupLib2, err := repo.LookupLdapCredentialLibrary(t.Context(), createdLib.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, lookupLib2)
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -180,6 +180,44 @@ func TestSSHCertificateCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping
|
||||
return libs
|
||||
}
|
||||
|
||||
// TestLdapCredentialLibraries creates `count` number of Vault LDAP credential
|
||||
// libraries in the provided db with the provided credential store. If any
|
||||
// errors are encountered, the test will fail.
|
||||
func TestLdapCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping.Wrapper, storeId string, count int) []*LdapCredentialLibrary {
|
||||
t.Helper()
|
||||
w := db.New(conn)
|
||||
|
||||
var libs []*LdapCredentialLibrary
|
||||
for i := range count {
|
||||
var staticOrDynamic string
|
||||
switch mrand.Intn(2) {
|
||||
case 0:
|
||||
staticOrDynamic = "static-cred"
|
||||
case 1:
|
||||
staticOrDynamic = "creds"
|
||||
}
|
||||
|
||||
lib, err := NewLdapCredentialLibrary(storeId, fmt.Sprintf("ldap/%s/role-%d", staticOrDynamic, i))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, lib)
|
||||
|
||||
libId, err := newLdapCredentialLibraryId(t.Context())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, libId)
|
||||
|
||||
lib.PublicId = libId
|
||||
_, err = w.DoTx(t.Context(), db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
return w.Create(t.Context(), lib)
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
libs = append(libs, lib)
|
||||
}
|
||||
|
||||
return libs
|
||||
}
|
||||
|
||||
// TestCredentials creates count number of vault credentials in the provided DB with
|
||||
// the provided library id and session id. If any errors are encountered
|
||||
// during the creation of the credentials, the test will fail.
|
||||
@@ -532,6 +570,10 @@ type testOptions struct {
|
||||
clientKey *ecdsa.PrivateKey
|
||||
vaultVersion string
|
||||
serverCertHostNames []string
|
||||
bindDn string
|
||||
bindPass string
|
||||
ldapUrl string
|
||||
ldapRootDomain string
|
||||
}
|
||||
|
||||
func getDefaultTestOptions(t testing.TB) testOptions {
|
||||
@@ -666,6 +708,40 @@ func WithAllowedExtension(e string) TestOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithBindDn sets the distinguished name to be used by the Vault LDAP secrets
|
||||
// engine.
|
||||
func WithBindDn(dn string) TestOption {
|
||||
return func(t testing.TB, o *testOptions) {
|
||||
t.Helper()
|
||||
o.bindDn = dn
|
||||
}
|
||||
}
|
||||
|
||||
// WithBindPass sets the password to be used by the Vault LDAP secrets engine.
|
||||
// It is also used to set the admin user password in the LDAP server.
|
||||
func WithBindPass(p string) TestOption {
|
||||
return func(t testing.TB, o *testOptions) {
|
||||
t.Helper()
|
||||
o.bindPass = p
|
||||
}
|
||||
}
|
||||
|
||||
// WithLdapUrl sets the URL to be used by the Vault LDAP secrets engine.
|
||||
func WithLdapUrl(u string) TestOption {
|
||||
return func(t testing.TB, o *testOptions) {
|
||||
t.Helper()
|
||||
o.ldapUrl = u
|
||||
}
|
||||
}
|
||||
|
||||
// WithLdapRootDomain controls the LDAP server's root domain.
|
||||
func WithLdapRootDomain(d string) TestOption {
|
||||
return func(t testing.TB, o *testOptions) {
|
||||
t.Helper()
|
||||
o.ldapRootDomain = d
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrphanToken sets the token orphan option to b.
|
||||
// The orphan option is true by default.
|
||||
func TestOrphanToken(b bool) TestOption {
|
||||
@@ -916,6 +992,81 @@ func (v *TestVaultServer) MountSSH(t testing.TB, opt ...TestOption) *vault.Secre
|
||||
return s
|
||||
}
|
||||
|
||||
// MountLdap mounts the Vault LDAP secrets engine on a Vault instance and
|
||||
// initializes it.
|
||||
//
|
||||
// Supported options include:
|
||||
// - WithTestMountPath: Sets the mount path. Defaults to "ldap".
|
||||
// - WithBindDn: Sets the BindDN to be used by the Vault LDAP secrets engine.
|
||||
// Defaults to "cn=admin,dc=example,dc=org".
|
||||
// - WithBindPass: Sets the password to be used by the Vault LDAP secrets
|
||||
// engine. Defaults to "admin".
|
||||
// - WithLdapUrl: Sets the URL of the LDAP server to be used by the Vault LDAP
|
||||
// secrets engine. Defaults to "ldap://ldap".
|
||||
//
|
||||
// MountLdap also adds a Vault policy named 'ldap' to the standard set of
|
||||
// policies attached to tokens created with v.CreateToken. The policy is defined
|
||||
// as:
|
||||
//
|
||||
// path "<ldapMountPath>/*" {
|
||||
// capabilities = ["create", "read", "update", "delete", "list"]
|
||||
// }
|
||||
func (v *TestVaultServer) MountLdap(t testing.TB, opt ...TestOption) {
|
||||
require := require.New(t)
|
||||
opts := getTestOpts(t, opt...)
|
||||
vc := v.client(t).cl
|
||||
|
||||
maxTTL := 24 * time.Hour
|
||||
if t, ok := t.(*testing.T); ok {
|
||||
if deadline, ok := t.Deadline(); ok {
|
||||
maxTTL = time.Until(deadline) * 2
|
||||
}
|
||||
}
|
||||
|
||||
defaultTTL := maxTTL / 2
|
||||
mountInput := &vault.MountInput{
|
||||
Type: "ldap",
|
||||
Description: t.Name(),
|
||||
Config: vault.MountConfigInput{
|
||||
DefaultLeaseTTL: defaultTTL.String(),
|
||||
MaxLeaseTTL: maxTTL.String(),
|
||||
},
|
||||
}
|
||||
mountPath := opts.mountPath
|
||||
if mountPath == "" {
|
||||
mountPath = "ldap/"
|
||||
}
|
||||
require.True(mountPath[len(mountPath)-1] == '/', "mount path must end with a slash")
|
||||
require.NoError(vc.Sys().Mount(mountPath, mountInput))
|
||||
|
||||
policyPath := fmt.Sprintf("%s*", mountPath)
|
||||
pc := pathCapabilities{
|
||||
policyPath: createCapability | readCapability | updateCapability | deleteCapability | listCapability,
|
||||
}
|
||||
v.addPolicy(t, "ldap", pc)
|
||||
|
||||
// Create and write LDAP server config
|
||||
bindDn := opts.bindDn
|
||||
if bindDn == "" {
|
||||
bindDn = "cn=admin,dc=example,dc=org"
|
||||
}
|
||||
bindPass := opts.bindPass
|
||||
if bindPass == "" {
|
||||
bindPass = "admin"
|
||||
}
|
||||
url := opts.ldapUrl
|
||||
if url == "" {
|
||||
url = "ldap://ldap"
|
||||
}
|
||||
ldapConfig := map[string]any{
|
||||
"binddn": bindDn,
|
||||
"bindpass": bindPass,
|
||||
"url": url,
|
||||
}
|
||||
_, err := vc.Logical().Write(path.Join(mountPath, "config"), ldapConfig)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// MountPKI mounts the Vault PKI secret engine and initializes it by
|
||||
// generating a root certificate authority and creating a default role on
|
||||
// the mount. The root CA is returned.
|
||||
@@ -1038,9 +1189,12 @@ type TestVaultServer struct {
|
||||
serverCertBundle *testCertBundle
|
||||
clientCertBundle *testCertBundle
|
||||
|
||||
// Use any here instead of *dockertest.[Pool|Network|Resource] to avoid
|
||||
// problems with dockertest not building for some netbsd architectures.
|
||||
pool any
|
||||
vaultContainer any
|
||||
network any
|
||||
vaultContainer any
|
||||
ldapContainer any
|
||||
postgresContainer any
|
||||
}
|
||||
|
||||
@@ -1115,3 +1269,39 @@ func (d *TestDatabase) ValidateCredential(t testing.TB, s *vault.Secret) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestLdapServer is returned from MountLdapServer and contains information
|
||||
// about how an LDAP server was setup. It is primarily used to test
|
||||
// Boundary <> Vault <> LDAP Server interactions.
|
||||
type TestLdapServer struct {
|
||||
BindDomain string
|
||||
BindPass string
|
||||
BindDn string
|
||||
Url string
|
||||
}
|
||||
|
||||
// MountLdapServer creates an LDAP server as a Docker container, within a
|
||||
// running Vault's Docker network. Additionally, it mounts and configures the
|
||||
// LDAP secrets engine in the passed in Vault instance.
|
||||
//
|
||||
// Supported options:
|
||||
// - WithBindPass. Controls the LDAP server's admin password. Defaults to
|
||||
// "admin".
|
||||
// - WithLdapRootDomain. Controls the LDAP server's root domain. Defaults to
|
||||
// "example.org".
|
||||
//
|
||||
// Some roles are added to the LDAP server as well as configured within Vault:
|
||||
// - scientists: A group of unique members: cn=scientists,dc=example,dc=org
|
||||
// - einstein: A user belonging to the scientists group: cn=einstein,dc=example,dc=org
|
||||
// - newton: Another user beloging to the scientists group: cn=newton,dc=example,dc=org
|
||||
//
|
||||
// Note that the domain components (dc) will change accordingly if WithDomain is
|
||||
// passed in.
|
||||
//
|
||||
// Dynamic roles are also setup using LDIF templates. These roles trigger Vault
|
||||
// to create users on-demand and manage their lifecycle. All dynamic users are
|
||||
// added to the "scientists" group.
|
||||
func MountLdapServer(t testing.TB, v *TestVaultServer, opt ...TestOption) *TestLdapServer {
|
||||
t.Helper()
|
||||
return mountLdapServer(t, v, opt...)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user