From 4b365db6341889a919a842b73b3ca8aa7300c650 Mon Sep 17 00:00:00 2001 From: Louis Ruch Date: Thu, 9 Jun 2022 15:35:55 -0700 Subject: [PATCH] feat(target): Add support for static credentials during authorize-session --- internal/cmd/commands/connect/funcs.go | 2 +- internal/cmd/commands/targetscmd/funcs.go | 2 +- internal/daemon/controller/handler.go | 3 +- .../handlers/targets/credentials.go | 198 ++++++++++++ .../handlers/targets/target_service.go | 191 +++++------- .../targets/tcp/target_service_test.go | 284 ++++++++++++------ .../oss/postgres/50/02_target.up.sql | 6 +- .../storage/target/store/v1/target.proto | 4 + internal/target/credential_source.go | 17 +- .../target/repository_credential_source.go | 14 +- internal/target/store/target.pb.go | 34 ++- 11 files changed, 519 insertions(+), 236 deletions(-) create mode 100644 internal/daemon/controller/handlers/targets/credentials.go diff --git a/internal/cmd/commands/connect/funcs.go b/internal/cmd/commands/connect/funcs.go index 3cf884b58..f44c789a5 100644 --- a/internal/cmd/commands/connect/funcs.go +++ b/internal/cmd/commands/connect/funcs.go @@ -78,7 +78,7 @@ func fmtSecretForTable(indent int, sc *targets.SessionCredential) []string { prefixStr := strings.Repeat(" ", indent) origSecret := []string{fmt.Sprintf("%s %s", prefixStr, sc.Secret.Raw)} switch sc.CredentialSource.Type { - case "vault": + case "vault", "static": if sc.Credential != nil { maxLength := 0 for k := range sc.Credential { diff --git a/internal/cmd/commands/targetscmd/funcs.go b/internal/cmd/commands/targetscmd/funcs.go index 385c2c174..77f8fed40 100644 --- a/internal/cmd/commands/targetscmd/funcs.go +++ b/internal/cmd/commands/targetscmd/funcs.go @@ -888,7 +888,7 @@ func printCustomActionOutputImpl(c *Command) (bool, error) { var secretStr []string switch cred.CredentialSource.Type { - case "vault": + case "vault", "static": switch { case cred.Credential != nil: maxLength := 0 diff --git a/internal/daemon/controller/handler.go b/internal/daemon/controller/handler.go index 0c9c0f973..54494928b 100644 --- a/internal/daemon/controller/handler.go +++ b/internal/daemon/controller/handler.go @@ -175,7 +175,8 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error { c.SessionRepoFn, c.PluginHostRepoFn, c.StaticHostRepoFn, - c.VaultCredentialRepoFn) + c.VaultCredentialRepoFn, + c.StaticCredentialRepoFn) if err != nil { return fmt.Errorf("failed to create target handler service: %w", err) } diff --git a/internal/daemon/controller/handlers/targets/credentials.go b/internal/daemon/controller/handlers/targets/credentials.go new file mode 100644 index 000000000..a67977b9e --- /dev/null +++ b/internal/daemon/controller/handlers/targets/credentials.go @@ -0,0 +1,198 @@ +package targets + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/hashicorp/boundary/internal/credential" + credstatic "github.com/hashicorp/boundary/internal/credential/static" + "github.com/hashicorp/boundary/internal/daemon/controller/handlers" + "github.com/hashicorp/boundary/internal/errors" + serverpb "github.com/hashicorp/boundary/internal/gen/controller/servers/services" + "github.com/hashicorp/boundary/internal/session" + "github.com/hashicorp/boundary/internal/types/subtypes" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +// dynamicToWorkerCredential converts the strongly typed credential.Dynamic into +// a session.Credential suitable for passing to a Boundary worker. +func dynamicToWorkerCredential(ctx context.Context, cred credential.Dynamic) (session.Credential, error) { + const op = "targets.dynamicToWorkerCredential" + var workerCred *serverpb.Credential + switch c := cred.(type) { + case credential.UsernamePassword: + workerCred = &serverpb.Credential{ + Credential: &serverpb.Credential_UserPassword{ + UserPassword: &serverpb.UserPassword{ + Username: c.Username(), + Password: string(c.Password()), + }, + }, + } + + default: + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c)) + } + + data, err := proto.Marshal(workerCred) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling dynamic secret to proto")) + } + return data, nil +} + +func dynamicToSessionCredential(ctx context.Context, cred credential.Dynamic) (*pb.SessionCredential, error) { + const op = "targets.dynamicToSessionCredential" + l := cred.Library() + secret := cred.Secret() + // TODO: Access the json directly from the vault response instead of re-marshalling it. + jSecret, err := json.Marshal(secret) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling secret to json")) + } + var sSecret *structpb.Struct + switch secret.(type) { + case map[string]interface{}: + // In this case we actually have to re-decode it. The proto wrappers + // choke on json.Number and at the time I'm writing this I don't + // have time to write a walk function to dig through with reflect + // and find all json.Numbers and replace them. So we eat the + // inefficiency. So note that we are specifically _not_ using a + // decoder with UseNumber here. + var dSecret map[string]interface{} + if err := json.Unmarshal(jSecret, &dSecret); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("decoding json for proto marshaling")) + } + sSecret, err = structpb.NewStruct(dSecret) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for secret")) + } + } + + var credType string + var credData *structpb.Struct + if l.CredentialType() != credential.UnspecifiedType { + credType = string(l.CredentialType()) + + switch c := cred.(type) { + case credential.UsernamePassword: + credData, err = handlers.ProtoToStruct( + &pb.UserPasswordCredential{ + Username: c.Username(), + Password: string(c.Password()), + }, + ) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential")) + } + + default: + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c)) + } + } + + return &pb.SessionCredential{ + CredentialLibrary: &pb.CredentialLibrary{ + Id: l.GetPublicId(), + Name: l.GetName(), + Description: l.GetDescription(), + CredentialStoreId: l.GetStoreId(), + Type: subtypes.SubtypeFromId(credentialDomain, l.GetPublicId()).String(), + }, + CredentialSource: &pb.CredentialSource{ + Id: l.GetPublicId(), + Name: l.GetName(), + Description: l.GetDescription(), + CredentialStoreId: l.GetStoreId(), + Type: subtypes.SubtypeFromId(credentialDomain, l.GetPublicId()).String(), + CredentialType: credType, + }, + Secret: &pb.SessionSecret{ + Raw: base64.StdEncoding.EncodeToString(jSecret), + Decoded: sSecret, + }, + Credential: credData, + }, nil +} + +// staticToWorkerCredential converts the credential.Static into +// a session.Credential suitable for passing to a Boundary worker. +func staticToWorkerCredential(ctx context.Context, cred credential.Static) (session.Credential, error) { + const op = "targets.staticToWorkerCredential" + var workerCred *serverpb.Credential + switch c := cred.(type) { + case credstatic.UsernamePasswordCredential: + workerCred = &serverpb.Credential{ + Credential: &serverpb.Credential_UserPassword{ + UserPassword: &serverpb.UserPassword{ + Username: c.GetUsername(), + Password: string(c.GetPassword()), + }, + }, + } + + default: + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c)) + } + + data, err := proto.Marshal(workerCred) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling dynamic secret to proto")) + } + return data, nil +} + +func staticToSessionCredential(ctx context.Context, cred credential.Static) (*pb.SessionCredential, error) { + const op = "targets.staticToSessionCredential" + + var credType string + var credData *structpb.Struct + var secret map[string]interface{} + switch c := cred.(type) { + case *credstatic.UsernamePasswordCredential: + var err error + credType = string(credential.UsernamePasswordType) + credData, err = handlers.ProtoToStruct( + &pb.UserPasswordCredential{ + Username: c.GetUsername(), + Password: string(c.GetPassword()), + }, + ) + secret = map[string]interface{}{ + "username": c.GetUsername(), + "password": string(c.GetPassword()), + } + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for static credential")) + } + + default: + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c)) + } + + jSecret, err := json.Marshal(secret) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling static secret to json")) + } + sSecret, err := structpb.NewStruct(secret) + + return &pb.SessionCredential{ + CredentialSource: &pb.CredentialSource{ + Id: cred.GetPublicId(), + Name: cred.GetName(), + Description: cred.GetDescription(), + CredentialStoreId: cred.GetStoreId(), + Type: credstatic.Subtype.String(), + CredentialType: credType, + }, + Secret: &pb.SessionSecret{ + Raw: base64.StdEncoding.EncodeToString(jSecret), + Decoded: sSecret, + }, + Credential: credData, + }, nil +} diff --git a/internal/daemon/controller/handlers/targets/target_service.go b/internal/daemon/controller/handlers/targets/target_service.go index 69fb4b0d0..9c85c058e 100644 --- a/internal/daemon/controller/handlers/targets/target_service.go +++ b/internal/daemon/controller/handlers/targets/target_service.go @@ -2,8 +2,6 @@ package targets import ( "context" - "encoding/base64" - "encoding/json" stderrors "errors" "fmt" "math/rand" @@ -23,7 +21,6 @@ import ( "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" - serverpb "github.com/hashicorp/boundary/internal/gen/controller/servers/services" "github.com/hashicorp/boundary/internal/host" "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" @@ -44,7 +41,6 @@ import ( "github.com/mr-tron/base58" "google.golang.org/grpc/codes" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -97,6 +93,7 @@ type Service struct { pluginHostRepoFn common.PluginHostRepoFactory staticHostRepoFn common.StaticRepoFactory vaultCredRepoFn common.VaultCredentialRepoFactory + staticCredRepoFn common.StaticCredentialRepoFactory kmsCache *kms.Kms } @@ -111,6 +108,7 @@ func NewService( pluginHostRepoFn common.PluginHostRepoFactory, staticHostRepoFn common.StaticRepoFactory, vaultCredRepoFn common.VaultCredentialRepoFactory, + staticCredRepoFn common.StaticCredentialRepoFactory, ) (Service, error) { const op = "targets.NewService" if repoFn == nil { @@ -134,6 +132,9 @@ func NewService( if vaultCredRepoFn == nil { return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing vault credential repository") } + if staticCredRepoFn == nil { + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing static credential repository") + } return Service{ repoFn: repoFn, iamRepoFn: iamRepoFn, @@ -142,6 +143,7 @@ func NewService( pluginHostRepoFn: pluginHostRepoFn, staticHostRepoFn: staticHostRepoFn, vaultCredRepoFn: vaultCredRepoFn, + staticCredRepoFn: staticCredRepoFn, kmsCache: kmsCache, }, nil } @@ -873,7 +875,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession if err != nil { return nil, err } - t, hostSources, libs, err := repo.LookupTarget(ctx, t.GetPublicId()) + t, hostSources, credSources, err := repo.LookupTarget(ctx, t.GetPublicId()) if err != nil { if errors.IsNotFoundError(err) { return nil, handlers.NotFoundErrorf("Target %q not found.", t.GetPublicId()) @@ -1049,14 +1051,22 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession Host: net.JoinHostPort(h, p), } - var reqs []credential.Request + var vaultReqs []credential.Request + var staticIds []string var dynCreds []*session.DynamicCredential - for _, l := range libs { - reqs = append(reqs, credential.Request{ - SourceId: l.Id(), - Purpose: l.CredentialPurpose(), - }) - dynCreds = append(dynCreds, session.NewDynamicCredential(l.Id(), l.CredentialPurpose())) + var staticCreds []*session.StaticCredential + for _, cs := range credSources { + switch cs.Type() { + case target.LibraryCredentialSourceType: + vaultReqs = append(vaultReqs, credential.Request{ + SourceId: cs.Id(), + Purpose: cs.CredentialPurpose(), + }) + dynCreds = append(dynCreds, session.NewDynamicCredential(cs.Id(), cs.CredentialPurpose())) + case target.StaticCredentialSourceType: + staticIds = append(staticIds, cs.Id()) + staticCreds = append(staticCreds, session.NewStaticCredential(cs.Id(), cs.CredentialPurpose())) + } } expTime := timestamppb.Now() @@ -1073,6 +1083,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession ConnectionLimit: t.GetSessionConnectionLimit(), WorkerFilter: t.GetWorkerFilter(), DynamicCredentials: dynCreds, + StaticCredentials: staticCreds, } sess, err := session.New(sessionComposition) @@ -1088,111 +1099,82 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession return nil, err } - var cs []credential.Dynamic - if len(reqs) > 0 { + var dynamic []credential.Dynamic + var staticCredsById map[string]credential.Static + if len(vaultReqs) > 0 { credRepo, err := s.vaultCredRepoFn() if err != nil { return nil, errors.Wrap(ctx, err, op) } - cs, err = credRepo.Issue(ctx, sess.GetPublicId(), reqs) + dynamic, err = credRepo.Issue(ctx, sess.GetPublicId(), vaultReqs) if err != nil { return nil, errors.Wrap(ctx, err, op) } } + if len(staticIds) > 0 { + credRepo, err := s.staticCredRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + // Remove duplicate requests + staticIds = strutil.RemoveDuplicates(staticIds, false) + creds, err := credRepo.Retrieve(ctx, t.GetScopeId(), staticIds) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + staticCredsById = make(map[string]credential.Static) + for _, c := range creds { + staticCredsById[c.GetPublicId()] = c + } + } + var creds []*pb.SessionCredential var workerCreds []session.Credential - for _, cred := range cs { + for _, cred := range dynamic { switch cred.Purpose() { case credential.EgressPurpose: - m, err := credentialToProto(ctx, cred) + c, err := dynamicToWorkerCredential(ctx, cred) if err != nil { return nil, errors.Wrap(ctx, err, op) } - data, err := proto.Marshal(m) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling secret to proto")) - } - workerCreds = append(workerCreds, data) + workerCreds = append(workerCreds, c) case credential.ApplicationPurpose: - l := cred.Library() - secret := cred.Secret() - // TODO: Access the json directly from the vault response instead of re-marshalling it. - jSecret, err := json.Marshal(secret) + c, err := dynamicToSessionCredential(ctx, cred) if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling secret to json")) + return nil, errors.Wrap(ctx, err, op) } - var sSecret *structpb.Struct - switch secret.(type) { - case map[string]interface{}: - // In this case we actually have to re-decode it. The proto wrappers - // choke on json.Number and at the time I'm writing this I don't - // have time to write a walk function to dig through with reflect - // and find all json.Numbers and replace them. So we eat the - // inefficiency. So note that we are specifically _not_ using a - // decoder with UseNumber here. - var dSecret map[string]interface{} - if err := json.Unmarshal(jSecret, &dSecret); err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("decoding json for proto marshaling")) - } - sSecret, err = structpb.NewStruct(dSecret) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for secret")) - } - } - - var credType string - var credData *structpb.Struct - if l.CredentialType() != credential.UnspecifiedType { - credType = string(l.CredentialType()) - - switch c := cred.(type) { - case credential.UserPassword: - credData, err = handlers.ProtoToStruct( - &pb.UserPasswordCredential{ - Username: c.Username(), - Password: string(c.Password()), - }, - ) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential")) - } - - default: - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unsupported credential type")) - } - - } - - creds = append(creds, &pb.SessionCredential{ - CredentialLibrary: &pb.CredentialLibrary{ - Id: l.GetPublicId(), - Name: l.GetName(), - Description: l.GetDescription(), - CredentialStoreId: l.GetStoreId(), - Type: subtypes.SubtypeFromId(credentialDomain, l.GetPublicId()).String(), - }, - CredentialSource: &pb.CredentialSource{ - Id: l.GetPublicId(), - Name: l.GetName(), - Description: l.GetDescription(), - CredentialStoreId: l.GetStoreId(), - Type: subtypes.SubtypeFromId(credentialDomain, l.GetPublicId()).String(), - CredentialType: credType, - }, - Secret: &pb.SessionSecret{ - Raw: base64.StdEncoding.EncodeToString(jSecret), - Decoded: sSecret, - }, - Credential: credData, - }) + creds = append(creds, c) default: return nil, errors.New(ctx, errors.Unknown, op, fmt.Sprintf("unsupported credential purpose %s", cred.Purpose())) } } + for _, sc := range staticCreds { + switch sc.CredentialPurpose { + case string(credential.EgressPurpose): + c, err := staticToWorkerCredential(ctx, staticCredsById[sc.CredentialStaticId]) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + workerCreds = append(workerCreds, c) + + case string(credential.ApplicationPurpose): + c, err := staticToSessionCredential(ctx, staticCredsById[sc.CredentialStaticId]) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + creds = append(creds, c) + + default: + return nil, errors.New(ctx, errors.Unknown, op, fmt.Sprintf("unsupported credential purpose %s", sc.CredentialPurpose)) + } + } + if len(workerCreds) > 0 { // store credentials in repo, worker will request creds when a connection is established err = sessionRepo.AddSessionCredentials(ctx, sess.ScopeId, sess.PublicId, workerCreds) @@ -1226,7 +1208,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession Scope: authResults.Scope, CreatedTime: sess.CreateTime.GetTimestamp(), Type: t.GetType().String(), - AuthorizationToken: string(encodedMarshaledSad), + AuthorizationToken: encodedMarshaledSad, UserId: authResults.UserId, HostId: chosenEndpoint.HostId, HostSetId: chosenEndpoint.SetId, @@ -1327,7 +1309,9 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st if err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err) } - u.SetPublicId(ctx, id) + if err := u.SetPublicId(ctx, id); err != nil { + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set target id: %v.", err) + } maskManager, err := subtypeRegistry.maskManager(subtype) if err != nil { @@ -1668,7 +1652,7 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou // ApplicationCredentialLibrariesField is deprecated and should only be populated // for library type sources - if cs.Type() == "library" { + if cs.Type() == target.LibraryCredentialSourceType { appCredLibIds = append(appCredLibIds, cs.Id()) appCredLibraries = append(appCredLibraries, &pb.CredentialLibrary{ Id: cs.Id(), @@ -2168,22 +2152,3 @@ func validateAuthorizeSessionRequest(req *pbs.AuthorizeSessionRequest) error { } return nil } - -// credentialToProto converts the strongly typed credential.Credential into a known proto.Message. -func credentialToProto(ctx context.Context, cred credential.Credential) (*serverpb.Credential, error) { - const op = "targets.credentialToProto" - switch c := cred.(type) { - case credential.UserPassword: - return &serverpb.Credential{ - Credential: &serverpb.Credential_UserPassword{ - UserPassword: &serverpb.UserPassword{ - Username: c.Username(), - Password: string(c.Password()), - }, - }, - }, nil - - default: - return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c)) - } -} diff --git a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go index 7e57aa99d..e8fe57ae8 100644 --- a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go +++ b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries" + "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/workers" "github.com/hashicorp/boundary/internal/db" @@ -39,7 +40,8 @@ import ( "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/internal/types/scope" - credpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries" + credlibpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries" + credpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets" plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" @@ -99,10 +101,13 @@ func testService(t *testing.T, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapp pluginHostRepoFn := func() (*plugin.Repository, error) { return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) } - credentialRepoFn := func() (*vault.Repository, error) { + vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } - return targets.NewService(context.Background(), kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) + staticCredRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } + return targets.NewService(context.Background(), kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn) } func TestGet(t *testing.T) { @@ -2436,8 +2441,8 @@ func TestSetTargetCredentialSources(t *testing.T) { resultLibIds []string }{ { - name: "Set libary on empty target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty libary"), + name: "Set library on empty target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty library"), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, resultLibIds: []string{cls[1].GetPublicId()}, @@ -2450,14 +2455,14 @@ func TestSetTargetCredentialSources(t *testing.T) { }, { name: "Set library on library populated target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated libary-libary", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, resultLibIds: []string{cls[1].GetPublicId()}, }, { name: "Set static on library populated target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-libary", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{creds[1].GetPublicId()}, resultCredentialSourceIds: []string{creds[1].GetPublicId()}, }, @@ -2813,9 +2818,12 @@ func TestAuthorizeSession(t *testing.T) { staticHostRepoFn := func() (*static.Repository, error) { return staticRepo, nil } - credentialRepoFn := func() (*vault.Repository, error) { + vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticCredRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(ctx, rw, rw, kms) + } atRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kms) } @@ -2889,15 +2897,15 @@ func TestAuthorizeSession(t *testing.T) { v.MountPKI(t) sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "pki"})) - store := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) - credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) + vaultStore := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) + credService, err := credentiallibraries.NewService(vaultCredRepoFn, iamRepoFn) require.NoError(t, err) - clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ - CredentialStoreId: store.GetPublicId(), + clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ + CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Library Name"), Description: wrapperspb.String("Library Description"), - Attrs: &credpb.CredentialLibrary_VaultCredentialLibraryAttributes{ - VaultCredentialLibraryAttributes: &credpb.VaultCredentialLibraryAttributes{ + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: &wrapperspb.StringValue{ Value: path.Join("pki", "issue", "boundary"), }, @@ -2943,7 +2951,7 @@ func TestAuthorizeSession(t *testing.T) { }, } - s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn) require.NoError(t, err) for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -3013,14 +3021,14 @@ func TestAuthorizeSession(t *testing.T) { Id: clsResp.GetItem().GetId(), Name: clsResp.GetItem().GetName().GetValue(), Description: clsResp.GetItem().GetDescription().GetValue(), - CredentialStoreId: store.GetPublicId(), + CredentialStoreId: vaultStore.GetPublicId(), Type: vault.Subtype.String(), }, CredentialSource: &pb.CredentialSource{ Id: clsResp.GetItem().GetId(), Name: clsResp.GetItem().GetName().GetValue(), Description: clsResp.GetItem().GetDescription().GetValue(), - CredentialStoreId: store.GetPublicId(), + CredentialStoreId: vaultStore.GetPublicId(), Type: vault.Subtype.String(), }, }}, @@ -3085,9 +3093,12 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { staticHostRepoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - credentialRepoFn := func() (*vault.Repository, error) { + vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticCredRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(ctx, rw, rw, kms) + } atRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kms) } @@ -3112,7 +3123,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*") - s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn) require.NoError(t, err) hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] @@ -3124,20 +3135,20 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { v.AddKVPolicy(t) sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "secret"})) - store := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) - credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) + vaultStore := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) + credLibService, err := credentiallibraries.NewService(vaultCredRepoFn, iamRepoFn) require.NoError(t, err) // Create secret in vault with default username and password fields defaultUserPass := v.CreateKVSecret(t, "default-userpass", []byte(`{"data": {"username": "my-user", "password": "my-pass"}}`)) require.NotNil(t, defaultUserPass) - clsRespUserPassword, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ - CredentialStoreId: store.GetPublicId(), + clsRespUserPassword, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ + CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Userpassword Library"), Description: wrapperspb.String("Userpassword Library Description"), - Attrs: &credpb.CredentialLibrary_VaultCredentialLibraryAttributes{ - VaultCredentialLibraryAttributes: &credpb.VaultCredentialLibraryAttributes{ + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "default-userpass")), HttpMethod: wrapperspb.String("GET"), }, @@ -3146,12 +3157,12 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { }}) require.NoError(t, err) - clsRespUnspecified, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ - CredentialStoreId: store.GetPublicId(), + clsRespUnspecified, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ + CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Unspecified Library"), Description: wrapperspb.String("Unspecified Library Description"), - Attrs: &credpb.CredentialLibrary_VaultCredentialLibraryAttributes{ - VaultCredentialLibraryAttributes: &credpb.VaultCredentialLibraryAttributes{ + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "default-userpass")), HttpMethod: wrapperspb.String("GET"), }, @@ -3163,12 +3174,12 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { nonDefaultUserPass := v.CreateKVSecret(t, "non-default-userpass", []byte(`{"data": {"non-default-user": "my-user", "non-default-pass": "my-pass"}}`)) require.NotNil(t, nonDefaultUserPass) - clsRespUserPasswordWithMapping, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ - CredentialStoreId: store.GetPublicId(), + clsRespUserPasswordWithMapping, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ + CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Userpassword Mapping Library"), Description: wrapperspb.String("Userpassword Mapping Library Description"), - Attrs: &credpb.CredentialLibrary_VaultCredentialLibraryAttributes{ - VaultCredentialLibraryAttributes: &credpb.VaultCredentialLibraryAttributes{ + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "non-default-userpass")), HttpMethod: wrapperspb.String("GET"), }, @@ -3181,55 +3192,146 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { }}) require.NoError(t, err) + staticStore := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) + credService, err := credentials.NewService(staticCredRepoFn, iamRepoFn) + require.NoError(t, err) + credResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{ + CredentialStoreId: staticStore.GetPublicId(), + Type: credstatic.UsernamePasswordSubtype.String(), + Name: wrapperspb.String("Cred Name"), + Description: wrapperspb.String("Cred Description"), + Attrs: &credpb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &credpb.UsernamePasswordAttributes{ + Username: wrapperspb.String("static-username"), + Password: wrapperspb.String("static-password"), + }, + }, + }}) + require.NoError(t, err) + require.NotNil(t, credResp) + cases := []struct { name string hostSourceId string - credLib *credpb.CredentialLibrary + credSourceId string wantedHostId string wantedEndpoint string - wantedCredType string - wantedCred *structpb.Struct + wantedCred *pb.SessionCredential }{ { - name: "unspecified", + name: "vault-unspecified", hostSourceId: shs.GetPublicId(), + credSourceId: clsRespUnspecified.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), - credLib: clsRespUnspecified.GetItem(), + wantedCred: &pb.SessionCredential{ + CredentialLibrary: &pb.CredentialLibrary{ + Id: clsRespUnspecified.GetItem().GetId(), + Name: clsRespUnspecified.GetItem().GetName().GetValue(), + Description: clsRespUnspecified.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.Subtype.String(), + }, + CredentialSource: &pb.CredentialSource{ + Id: clsRespUnspecified.GetItem().GetId(), + Name: clsRespUnspecified.GetItem().GetName().GetValue(), + Description: clsRespUnspecified.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.Subtype.String(), + }, + }, }, { - name: "userpassword", + name: "vault-userpassword", hostSourceId: shs.GetPublicId(), + credSourceId: clsRespUserPassword.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), - credLib: clsRespUserPassword.GetItem(), - wantedCred: func() *structpb.Struct { - data := map[string]interface{}{ - "username": "my-user", - "password": "my-pass", - } - st, err := structpb.NewStruct(data) - require.NoError(t, err) - return st - }(), - wantedCredType: string(credential.UsernamePasswordType), + wantedCred: &pb.SessionCredential{ + CredentialLibrary: &pb.CredentialLibrary{ + Id: clsRespUserPassword.GetItem().GetId(), + Name: clsRespUserPassword.GetItem().GetName().GetValue(), + Description: clsRespUserPassword.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.Subtype.String(), + }, + CredentialSource: &pb.CredentialSource{ + Id: clsRespUserPassword.GetItem().GetId(), + Name: clsRespUserPassword.GetItem().GetName().GetValue(), + Description: clsRespUserPassword.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.Subtype.String(), + CredentialType: string(credential.UsernamePasswordType), + }, + Credential: func() *structpb.Struct { + data := map[string]interface{}{ + "password": "my-pass", + "username": "my-user", + } + st, err := structpb.NewStruct(data) + require.NoError(t, err) + return st + }(), + }, }, { - name: "userpassword-with-mapping", + name: "vault-userpassword-with-mapping", hostSourceId: shs.GetPublicId(), + credSourceId: clsRespUserPasswordWithMapping.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), - credLib: clsRespUserPasswordWithMapping.GetItem(), - wantedCred: func() *structpb.Struct { - data := map[string]interface{}{ - "password": "my-pass", - "username": "my-user", - } - st, err := structpb.NewStruct(data) - require.NoError(t, err) - return st - }(), - wantedCredType: string(credential.UsernamePasswordType), + wantedCred: &pb.SessionCredential{ + CredentialLibrary: &pb.CredentialLibrary{ + Id: clsRespUserPasswordWithMapping.GetItem().GetId(), + Name: clsRespUserPasswordWithMapping.GetItem().GetName().GetValue(), + Description: clsRespUserPasswordWithMapping.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.Subtype.String(), + }, + CredentialSource: &pb.CredentialSource{ + Id: clsRespUserPasswordWithMapping.GetItem().GetId(), + Name: clsRespUserPasswordWithMapping.GetItem().GetName().GetValue(), + Description: clsRespUserPasswordWithMapping.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.Subtype.String(), + CredentialType: string(credential.UsernamePasswordType), + }, + Credential: func() *structpb.Struct { + data := map[string]interface{}{ + "password": "my-pass", + "username": "my-user", + } + st, err := structpb.NewStruct(data) + require.NoError(t, err) + return st + }(), + }, + }, + { + name: "static-userpassword", + hostSourceId: shs.GetPublicId(), + credSourceId: credResp.GetItem().GetId(), + wantedHostId: h.GetPublicId(), + wantedEndpoint: h.GetAddress(), + wantedCred: &pb.SessionCredential{ + CredentialSource: &pb.CredentialSource{ + Id: credResp.GetItem().GetId(), + Name: credResp.GetItem().GetName().GetValue(), + Description: credResp.GetItem().GetDescription().GetValue(), + CredentialStoreId: staticStore.GetPublicId(), + Type: credstatic.Subtype.String(), + CredentialType: string(credential.UsernamePasswordType), + }, + Credential: func() *structpb.Struct { + data := map[string]interface{}{ + "password": "static-password", + "username": "static-username", + } + st, err := structpb.NewStruct(data) + require.NoError(t, err) + return st + }(), + }, }, } @@ -3246,7 +3348,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { _, err = s.AddTargetCredentialSources(ctx, &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), - ApplicationCredentialSourceIds: []string{tc.credLib.GetId()}, + ApplicationCredentialSourceIds: []string{tc.credSourceId}, Version: apiTar.GetItem().GetVersion(), }) require.NoError(t, err) @@ -3274,32 +3376,13 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { Description: proj.GetDescription(), ParentScopeId: proj.GetParentId(), }, - TargetId: tar.GetPublicId(), - UserId: at.GetIamUserId(), - HostSetId: tc.hostSourceId, - HostId: tc.wantedHostId, - Type: "tcp", - Endpoint: fmt.Sprintf("tcp://%s:%d", tc.wantedEndpoint, defaultPort), - Credentials: []*pb.SessionCredential{ - { - CredentialLibrary: &pb.CredentialLibrary{ - Id: tc.credLib.GetId(), - Name: tc.credLib.GetName().GetValue(), - Description: tc.credLib.GetDescription().GetValue(), - CredentialStoreId: store.GetPublicId(), - Type: vault.Subtype.String(), - }, - CredentialSource: &pb.CredentialSource{ - Id: tc.credLib.GetId(), - Name: tc.credLib.GetName().GetValue(), - Description: tc.credLib.GetDescription().GetValue(), - CredentialStoreId: store.GetPublicId(), - Type: vault.Subtype.String(), - CredentialType: tc.wantedCredType, - }, - Credential: tc.wantedCred, - }, - }, + TargetId: tar.GetPublicId(), + UserId: at.GetIamUserId(), + HostSetId: tc.hostSourceId, + HostId: tc.wantedHostId, + Type: "tcp", + Endpoint: fmt.Sprintf("tcp://%s:%d", tc.wantedEndpoint, defaultPort), + Credentials: []*pb.SessionCredential{tc.wantedCred}, // TODO: validate the contents of the authorization token is what is expected } got := asRes.GetItem() @@ -3350,15 +3433,18 @@ func TestAuthorizeSession_Errors(t *testing.T) { pluginHostRepoFn := func() (*plugin.Repository, error) { return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) } - credentialRepoFn := func() (*vault.Repository, error) { + vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticCredRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(ctx, rw, rw, kms) + } atRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) - s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn) require.NoError(t, err) // Authorized user gets full permissions @@ -3441,13 +3527,13 @@ func TestAuthorizeSession_Errors(t *testing.T) { } libraryExists := func(tar target.Target) (version uint32) { - credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) + credService, err := credentiallibraries.NewService(vaultCredRepoFn, iamRepoFn) require.NoError(t, err) - clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ + clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: store.GetPublicId(), Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())), - Attrs: &credpb.CredentialLibrary_VaultCredentialLibraryAttributes{ - VaultCredentialLibraryAttributes: &credpb.VaultCredentialLibraryAttributes{ + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("database", "creds", "opened")), }, }, @@ -3465,13 +3551,13 @@ func TestAuthorizeSession_Errors(t *testing.T) { } misConfiguredlibraryExists := func(tar target.Target) (version uint32) { - credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) + credService, err := credentiallibraries.NewService(vaultCredRepoFn, iamRepoFn) require.NoError(t, err) - clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ + clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: store.GetPublicId(), Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())), - Attrs: &credpb.CredentialLibrary_VaultCredentialLibraryAttributes{ - VaultCredentialLibraryAttributes: &credpb.VaultCredentialLibraryAttributes{ + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String("bad path"), }, }, diff --git a/internal/db/schema/migrations/oss/postgres/50/02_target.up.sql b/internal/db/schema/migrations/oss/postgres/50/02_target.up.sql index c297565f7..8b3e51330 100644 --- a/internal/db/schema/migrations/oss/postgres/50/02_target.up.sql +++ b/internal/db/schema/migrations/oss/postgres/50/02_target.up.sql @@ -40,7 +40,8 @@ begin; tcl.target_id, tcl.credential_library_id as credential_source_id, tcl.credential_purpose, - cl.store_id + cl.store_id, + 'library' as type from target_credential_library tcl, credential_library cl @@ -51,7 +52,8 @@ begin; tcs.target_id, tcs.credential_static_id as credential_source_id, tcs.credential_purpose, - cst.store_id + cst.store_id, + 'static' as type from target_static_credential tcs, credential_static cst diff --git a/internal/proto/controller/storage/target/store/v1/target.proto b/internal/proto/controller/storage/target/store/v1/target.proto index 9b5c38195..afc02628b 100644 --- a/internal/proto/controller/storage/target/store/v1/target.proto +++ b/internal/proto/controller/storage/target/store/v1/target.proto @@ -125,6 +125,10 @@ message CredentialSource { // create_time from the RDBMS // @inject_tag: `gorm:"default:current_timestamp"` timestamp.v1.Timestamp create_time = 40; + + // type of credential source (library or static) + // @inject_tag: `gorm:"not_null"` + string type = 50; } message CredentialSourceView { diff --git a/internal/target/credential_source.go b/internal/target/credential_source.go index 88572a3e6..fcad085b7 100644 --- a/internal/target/credential_source.go +++ b/internal/target/credential_source.go @@ -5,6 +5,15 @@ import ( "github.com/hashicorp/boundary/internal/target/store" ) +// CredentialSourceType is the type of credential source. +type CredentialSourceType string + +// Credential source type values. +const ( + LibraryCredentialSourceType CredentialSourceType = "library" + StaticCredentialSourceType CredentialSourceType = "static" +) + var _ CredentialSource = (*TargetCredentialSource)(nil) // CredentialSource is an interface that can be implemented by both a library @@ -14,10 +23,11 @@ type CredentialSource interface { Id() string CredentialPurpose() credential.Purpose TargetId() string + Type() CredentialSourceType } // CredentialSources contains slices of credential publicIds -// per purpose to be attacehd to the target. +// per purpose to be attached to the target. type CredentialSources struct { ApplicationCredentialIds []string EgressCredentialIds []string @@ -58,6 +68,11 @@ func (ts *TargetCredentialSource) TargetId() string { return ts.GetTargetId() } +// Type returns the type of the credential source (library or static) +func (ts *TargetCredentialSource) Type() CredentialSourceType { + return CredentialSourceType(ts.GetType()) +} + // credentialSourceView provides a common way to return credential sources regardless of their // underlying type (library or static). type credentialSourceView struct { diff --git a/internal/target/repository_credential_source.go b/internal/target/repository_credential_source.go index a32c655b1..bcd2c65a7 100644 --- a/internal/target/repository_credential_source.go +++ b/internal/target/repository_credential_source.go @@ -478,8 +478,8 @@ func (r *Repository) changes(ctx context.Context, targetId string, ids []string, if err := r.reader.ScanRows(ctx, rows, &chg); err != nil { return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed")) } - switch chg.Type { - case "library": + switch CredentialSourceType(chg.Type) { + case LibraryCredentialSourceType: lib, err := NewCredentialLibrary(targetId, chg.SourceId, purpose) if err != nil { return nil, nil, nil, nil, errors.Wrap(ctx, err, op) @@ -490,7 +490,7 @@ func (r *Repository) changes(ctx context.Context, targetId string, ids []string, default: addCredLib = append(addCredLib, lib) } - case "static": + case StaticCredentialSourceType: cred, err := NewStaticCredential(targetId, chg.SourceId, purpose) if err != nil { return nil, nil, nil, nil, errors.Wrap(ctx, err, op) @@ -547,9 +547,9 @@ func (r *Repository) createSources(ctx context.Context, tId string, tSubtype sub // Create a map between credential source ID and it's type (library or static). // This will allow for a quick lookup when calling the corresponding New below - credTypeById := make(map[string]string, len(ids)) + credTypeById := make(map[string]CredentialSourceType, len(ids)) for _, cv := range credView { - credTypeById[cv.GetPublicId()] = cv.GetType() + credTypeById[cv.GetPublicId()] = CredentialSourceType(cv.GetType()) } credLibs := make([]*CredentialLibrary, 0, totalCreds) @@ -561,13 +561,13 @@ func (r *Repository) createSources(ctx context.Context, tId string, tSubtype sub for purpose, ids := range byPurpose { for _, id := range ids { switch credTypeById[id] { - case "library": + case LibraryCredentialSourceType: lib, err := NewCredentialLibrary(tId, id, purpose) if err != nil { return nil, nil, errors.Wrap(ctx, err, op) } credLibs = append(credLibs, lib) - case "static": + case StaticCredentialSourceType: cred, err := NewStaticCredential(tId, id, purpose) if err != nil { return nil, nil, errors.Wrap(ctx, err, op) diff --git a/internal/target/store/target.pb.go b/internal/target/store/target.pb.go index 9df9a13d2..4eed83939 100644 --- a/internal/target/store/target.pb.go +++ b/internal/target/store/target.pb.go @@ -427,6 +427,9 @@ type CredentialSource struct { // create_time from the RDBMS // @inject_tag: `gorm:"default:current_timestamp"` CreateTime *timestamp.Timestamp `protobuf:"bytes,40,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // type of credential source (library or static) + // @inject_tag: `gorm:"not_null"` + Type string `protobuf:"bytes,50,opt,name=type,proto3" json:"type,omitempty" gorm:"not_null"` } func (x *CredentialSource) Reset() { @@ -489,6 +492,13 @@ func (x *CredentialSource) GetCreateTime() *timestamp.Timestamp { return nil } +func (x *CredentialSource) GetType() string { + if x != nil { + return x.Type + } + return "" +} + type CredentialSourceView struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -628,7 +638,7 @@ var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, - 0xdd, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, + 0xf1, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, @@ -641,16 +651,18 @@ var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, - 0x47, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x47, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x3b, 0x5a, 0x39, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var (