diff --git a/.circleci/config.yml b/.circleci/config.yml index 0614f6975..8e2cc3e37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1055,7 +1055,7 @@ jobs: - run: command: | which pg_isready || sudo apt-get update && sudo apt-get install -y postgresql-client - make DOCKER_ARGS='-d' -C testing/dbtest/docker database-up + make DOCKER_ARGS='-d' PG_OPTS='-c shared_buffers=256MB -c max_connections=200000' -C testing/dbtest/docker database-up until pg_isready -h 127.0.0.1; do docker container inspect boundary-sql-tests &> /dev/null || exit -1; sleep 1; done name: Initialize Test Database - run: @@ -1267,6 +1267,17 @@ jobs: name: Save package cache paths: - .buildcache/packages/store + trigger-merge-to-ent: + machine: + image: ubuntu-2004:202107-02 + resource_class: medium + working_directory: ~/boundary + steps: + - checkout + - run: + command: | + ./scripts/trigger-merge-to-ent + name: Trigger Merge to Enterprise openbsd_amd64_package: docker: - image: docker.mirror.hashicorp.services/circleci/buildpack-deps @@ -1648,4 +1659,11 @@ workflows: branches: only: - stable-website + trigger-merge-to-ent: + jobs: + - trigger-merge-to-ent: + filters: + branches: + only: + - main version: 2 diff --git a/.circleci/config/jobs/build.yml b/.circleci/config/jobs/build.yml index 9efbe0e0c..08880157b 100644 --- a/.circleci/config/jobs/build.yml +++ b/.circleci/config/jobs/build.yml @@ -5,7 +5,7 @@ steps: name: "Initialize Test Database" command: | which pg_isready || sudo apt-get update && sudo apt-get install -y postgresql-client - make DOCKER_ARGS='-d' -C testing/dbtest/docker database-up + make DOCKER_ARGS='-d' PG_OPTS='-c shared_buffers=256MB -c max_connections=200000' -C testing/dbtest/docker database-up until pg_isready -h 127.0.0.1; do docker container inspect boundary-sql-tests &> /dev/null || exit -1; sleep 1; done - run: name: "Run Acceptance Tests" diff --git a/.circleci/config/jobs/trigger-merge-to-ent.yml b/.circleci/config/jobs/trigger-merge-to-ent.yml new file mode 100644 index 000000000..b976eab5c --- /dev/null +++ b/.circleci/config/jobs/trigger-merge-to-ent.yml @@ -0,0 +1,7 @@ +executor: go-machine-medium +steps: +- checkout +- run: + name: "Trigger Merge to Enterprise" + command: | + ./scripts/trigger-merge-to-ent diff --git a/.circleci/config/workflows/trigger-merge-to-ent.yml b/.circleci/config/workflows/trigger-merge-to-ent.yml new file mode 100644 index 000000000..12edc67ce --- /dev/null +++ b/.circleci/config/workflows/trigger-merge-to-ent.yml @@ -0,0 +1,6 @@ +jobs: + - trigger-merge-to-ent: + filters: + branches: + only: + - main diff --git a/.hooks/pre-push b/.hooks/pre-push new file mode 100755 index 000000000..1cb94652d --- /dev/null +++ b/.hooks/pre-push @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +remote="$1" +remote_url=$(git remote get-url $remote) + +if [[ $remote_url == *"boundary-enterprise"* ]]; then + exit 0 +fi + +if [ -f cmd/boundary/package_registration_ent.go ]; then + echo "Found enterprise file while pushing to oss remote" + exit 1 +fi + +exit 0 diff --git a/Makefile b/Makefile index e228e39a7..9b060bfef 100644 --- a/Makefile +++ b/Makefile @@ -127,6 +127,7 @@ protobuild: @protoc-go-inject-tag -input=./internal/kms/store/session_key.pb.go @protoc-go-inject-tag -input=./internal/kms/store/oidc_key.pb.go @protoc-go-inject-tag -input=./internal/target/store/target.pb.go + @protoc-go-inject-tag -input=./internal/target/tcp/store/target.pb.go @protoc-go-inject-tag -input=./internal/auth/oidc/store/oidc.pb.go @protoc-go-inject-tag -input=./internal/scheduler/job/store/job.pb.go @protoc-go-inject-tag -input=./internal/credential/store/credential.pb.go diff --git a/cmd/boundary/.gitignore b/cmd/boundary/.gitignore new file mode 100644 index 000000000..db02d202c --- /dev/null +++ b/cmd/boundary/.gitignore @@ -0,0 +1 @@ +/boundary diff --git a/cmd/boundary/package_registry_oss.go b/cmd/boundary/package_registry_oss.go new file mode 100644 index 000000000..65616ff18 --- /dev/null +++ b/cmd/boundary/package_registry_oss.go @@ -0,0 +1,6 @@ +package main + +import ( + // Enable tcp target support. + _ "github.com/hashicorp/boundary/internal/target/tcp" +) diff --git a/go.mod b/go.mod index c5528515d..af6584489 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,12 @@ require ( github.com/fatih/color v1.12.0 github.com/fatih/structs v1.1.0 github.com/favadi/protoc-go-inject-tag v1.3.0 + github.com/godbus/dbus/v5 v5.0.4 // indirect github.com/golang-migrate/migrate/v4 v4.14.1 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.6 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 github.com/hashicorp/boundary/api v0.0.19 github.com/hashicorp/boundary/sdk v0.0.12-0.20211007161358-65f3725bf3e1 diff --git a/go.sum b/go.sum index 6ee21fb45..ee9a46e93 100644 --- a/go.sum +++ b/go.sum @@ -341,8 +341,9 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -451,6 +452,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -1438,6 +1441,7 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= diff --git a/internal/auth/additional_verification_test.go b/internal/auth/additional_verification_test.go index a73c21f4b..2f0b6e633 100644 --- a/internal/auth/additional_verification_test.go +++ b/internal/auth/additional_verification_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/boundary/api/authtokens" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/servers" "github.com/hashicorp/boundary/internal/servers/controller" @@ -92,10 +93,10 @@ func TestFetchActionSetForId(t *testing.T) { authTokenRepoFn, serversRepoFn, tc.Kms(), - auth.RequestInfo{ + &authpb.RequestInfo{ PublicId: token.Id, EncryptedToken: strings.Split(token.Token, "_")[2], - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), }) typ := resource.Target if tt.typeOverride != resource.Unknown { diff --git a/internal/auth/oidc/repository_auth_method.go b/internal/auth/oidc/repository_auth_method.go index da8dbb569..cadba1dd7 100644 --- a/internal/auth/oidc/repository_auth_method.go +++ b/internal/auth/oidc/repository_auth_method.go @@ -2,11 +2,9 @@ package oidc import ( "context" - "database/sql" "encoding/json" "fmt" "net/url" - "strings" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" @@ -72,148 +70,84 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC if err != nil { return nil, errors.Wrap(ctx, err, op) } - - columns := []string{"public_id", "auth_method_id", "issuer", "subject"} - values := []interface{}{ - sql.Named("1", pubId), - sql.Named("2", am.PublicId), - sql.Named("3", iss), - sql.Named("4", sub), - } - var conflictClauses, fieldMasks, nullMasks []string - - { - marshaledTokenClaims, err := json.Marshal(IdTokenClaims) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - columns, values = append(columns, "token_claims"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), string(marshaledTokenClaims))) - conflictClauses = append(conflictClauses, fmt.Sprintf("token_claims = @%d", len(values))) - fieldMasks = append(fieldMasks, TokenClaimsField) - } - { - marshaledAccessTokenClaims, err := json.Marshal(AccessTokenClaims) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - columns, values = append(columns, "userinfo_claims"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), string(marshaledAccessTokenClaims))) - conflictClauses = append(conflictClauses, fmt.Sprintf("userinfo_claims = @%d", len(values))) - fieldMasks = append(fieldMasks, UserinfoClaimsField) - } - issAsUrl, err := url.Parse(iss) if err != nil { return nil, errors.New(ctx, errors.Unknown, op, "unable to parse issuer", errors.WithWrap(err)) } - acctForOplog, err := NewAccount(ctx, am.PublicId, sub, WithIssuer(issAsUrl)) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create new acct for oplog")) + + acct := AllocAccount() + acct.PublicId = pubId + acct.AuthMethodId = am.PublicId + acct.Issuer = issAsUrl.String() + acct.Subject = sub + + oc := db.OnConflict{ + Target: db.Constraint("auth_oidc_account_auth_method_id_issuer_subject_uq"), + Action: db.SetColumns([]string{"public_id", "auth_method_id", "issuer", "subject", "userinfo_claims", "token_claims"}), } - var foundName interface{} + marshaledTokenClaims, err := json.Marshal(IdTokenClaims) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + acct.TokenClaims = string(marshaledTokenClaims) + + marshaledAccessTokenClaims, err := json.Marshal(AccessTokenClaims) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + acct.UserinfoClaims = string(marshaledAccessTokenClaims) + + var foundName, foundEmail bool switch { case AccessTokenClaims[fromName] != nil: - foundName = AccessTokenClaims[fromName] - columns, values = append(columns, "full_name"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundName)) + foundName = true + acct.FullName = AccessTokenClaims[fromName].(string) case IdTokenClaims[fromName] != nil: - foundName = IdTokenClaims[fromName] - columns, values = append(columns, "full_name"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundName)) - } - if foundName != nil { - acctForOplog.FullName = foundName.(string) - conflictClauses = append(conflictClauses, fmt.Sprintf("full_name = @%d", len(values))) - fieldMasks = append(fieldMasks, NameField) - } else { - conflictClauses = append(conflictClauses, "full_name = NULL") - nullMasks = append(nullMasks, NameField) + foundName = true + acct.FullName = IdTokenClaims[fromName].(string) } - var foundEmail interface{} switch { case AccessTokenClaims[fromEmail] != nil: - foundEmail = AccessTokenClaims[fromEmail] - columns, values = append(columns, "email"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundEmail)) + foundEmail = true + acct.Email = AccessTokenClaims[fromEmail].(string) case IdTokenClaims[fromEmail] != nil: - foundEmail = IdTokenClaims[fromEmail] - columns, values = append(columns, "email"), append(values, sql.Named(fmt.Sprintf("%d", len(values)+1), foundEmail)) + foundEmail = true + acct.Email = IdTokenClaims[fromEmail].(string) } - if foundEmail != nil { - acctForOplog.Email = foundEmail.(string) - conflictClauses = append(conflictClauses, fmt.Sprintf("email = @%d", len(values))) - fieldMasks = append(fieldMasks, "Email") - } else { - conflictClauses = append(conflictClauses, "email = NULL") - nullMasks = append(nullMasks, "Email") + if foundName { + oc.Action = append(oc.Action.([]db.ColumnValue), db.SetColumns([]string{"full_name"})...) } - - placeHolders := make([]string, 0, len(columns)) - for colNum := range columns { - placeHolders = append(placeHolders, fmt.Sprintf("@%d", colNum+1)) + if foundEmail { + oc.Action = append(oc.Action.([]db.ColumnValue), db.SetColumns([]string{"email"})...) } - query := fmt.Sprintf(acctUpsertQuery, strings.Join(columns, ", "), strings.Join(placeHolders, ", "), strings.Join(conflictClauses, ", ")) oplogWrapper, err := r.kms.GetWrapper(ctx, am.ScopeId, kms.KeyPurposeOplog) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) } - updatedAcct := AllocAccount() _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { - var err error - rows, err := w.Query(ctx, query, values) + var rowCnt int64 + err := w.Create(ctx, acct, db.WithOnConflict(&oc), db.WithReturnRowsAffected(&rowCnt), db.WithOplog(oplogWrapper, acct.oplog(oplog.OpType_OP_TYPE_CREATE, am.ScopeId))) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to insert/update auth oidc account")) } - defer rows.Close() - result := struct { - PublicId string - Version int - }{} - var rowCnt int - for rows.Next() { - rowCnt += 1 - err = r.reader.ScanRows(rows, &result) - if err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to scan rows for account")) - } - } if rowCnt > 1 { return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 row but got: %d", rowCnt)) } - if err := reader.LookupWhere(ctx, &updatedAcct, "auth_method_id = ? and issuer = ? and subject = ?", am.PublicId, iss, sub); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to look up auth oidc account for: %s / %s / %s", am.PublicId, iss, sub))) - } - // include the version incase of predictable account public ids based on a calculation using authmethod id and subject - if result.Version == 1 && updatedAcct.PublicId == pubId { - if err := upsertOplog(ctx, w, oplogWrapper, oplog.OpType_OP_TYPE_CREATE, am.ScopeId, updatedAcct, nil, nil); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write create oplog for account")) - } - } else { - if len(fieldMasks) > 0 || len(nullMasks) > 0 { - acctForOplog := AllocAccount() - acctForOplog.PublicId = updatedAcct.PublicId - if foundEmail != nil { - acctForOplog.Email = foundEmail.(string) - } - if foundName != nil { - acctForOplog.FullName = foundName.(string) - } - if err := upsertOplog(ctx, w, oplogWrapper, oplog.OpType_OP_TYPE_UPDATE, am.ScopeId, acctForOplog, fieldMasks, nullMasks); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write update oplog for account")) - } - } - } return nil }, ) if err != nil { return nil, errors.Wrap(ctx, err, op) } - return updatedAcct, nil + return acct, nil } // upsertOplog will write oplog msgs for account upserts. The db.Writer needs to be the writer for the current diff --git a/internal/auth/password/repository_account.go b/internal/auth/password/repository_account.go index 3bb78540f..4bd0beb07 100644 --- a/internal/auth/password/repository_account.go +++ b/internal/auth/password/repository_account.go @@ -212,7 +212,7 @@ func validLoginName(u string) bool { if u == "" { return false } - return !reInvalidLoginName.Match([]byte(u)) + return !reInvalidLoginName.MatchString(u) } // UpdateAccount updates the repository entry for a.PublicId with the diff --git a/internal/cmd/base/initial_resources.go b/internal/cmd/base/initial_resources.go index f57319ae0..d6d7ce65e 100644 --- a/internal/cmd/base/initial_resources.go +++ b/internal/cmd/base/initial_resources.go @@ -13,6 +13,7 @@ import ( hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/internal/types/scope" plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/hashicorp/go-secure-stdlib/base62" @@ -497,7 +498,7 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error) // Host Catalog if b.DevTargetId == "" { - b.DevTargetId, err = db.NewPublicId(target.TcpTargetPrefix) + b.DevTargetId, err = db.NewPublicId(tcp.TargetPrefix) if err != nil { return nil, fmt.Errorf("error generating initial target id: %w", err) } @@ -514,11 +515,11 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error) target.WithSessionConnectionLimit(int32(b.DevTargetSessionConnectionLimit)), target.WithPublicId(b.DevTargetId), } - t, err := target.NewTcpTarget(b.DevProjectId, opts...) + t, err := tcp.New(b.DevProjectId, opts...) if err != nil { return nil, fmt.Errorf("error creating in memory target: %w", err) } - tt, _, _, err := targetRepo.CreateTcpTarget(cancelCtx, t, opts...) + tt, _, _, err := targetRepo.CreateTarget(cancelCtx, t, opts...) if err != nil { return nil, fmt.Errorf("error saving target to the db: %w", err) } diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index f8816e6b1..b6de8cd7c 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -23,7 +23,7 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/servers/worker" - "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/go-secure-stdlib/parseutil" "github.com/hashicorp/go-secure-stdlib/strutil" @@ -397,7 +397,7 @@ func (c *Command) Run(args []string) int { c.DevHostCatalogId = fmt.Sprintf("%s_%s", static.HostCatalogPrefix, c.flagIdSuffix) c.DevHostSetId = fmt.Sprintf("%s_%s", static.HostSetPrefix, c.flagIdSuffix) c.DevHostId = fmt.Sprintf("%s_%s", static.HostPrefix, c.flagIdSuffix) - c.DevTargetId = fmt.Sprintf("%s_%s", target.TcpTargetPrefix, c.flagIdSuffix) + c.DevTargetId = fmt.Sprintf("%s_%s", tcp.TargetPrefix, c.flagIdSuffix) } host, port, err := net.SplitHostPort(c.flagHostAddress) diff --git a/internal/cmd/common/flags.go b/internal/cmd/common/flags.go index f7b197896..9c022b383 100644 --- a/internal/cmd/common/flags.go +++ b/internal/cmd/common/flags.go @@ -301,7 +301,7 @@ func HandleAttributeFlags(c *base.Command, suffix, fullField string, sepFields [ case strings.HasPrefix(field.Value, `"`): // explicitly quoted string val = strings.Trim(field.Value, `"`) - case jsonNumberRegex.Match([]byte(strings.Trim(field.Value, `"`))): // number + case jsonNumberRegex.MatchString(strings.Trim(field.Value, `"`)): // number // Same logic as above if strings.Contains(field.Value, ".") { val, err = strconv.ParseFloat(field.Value, 64) diff --git a/internal/credential/vault/jobs_test.go b/internal/credential/vault/jobs_test.go index 3ceeaadf2..b6f16e73e 100644 --- a/internal/credential/vault/jobs_test.go +++ b/internal/credential/vault/jobs_test.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" wrapping "github.com/hashicorp/go-kms-wrapping" vault "github.com/hashicorp/vault/api" "github.com/stretchr/testify/assert" @@ -815,7 +816,7 @@ func TestTokenRevocationJob_Run(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, @@ -1008,7 +1009,7 @@ func TestCredentialRenewalJob_RunLimits(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, @@ -1117,7 +1118,7 @@ func TestCredentialRenewalJob_Run(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, HostId: h.GetPublicId(), @@ -1223,7 +1224,7 @@ func TestCredentialRenewalJob_RunExpired(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, HostId: h.GetPublicId(), @@ -1296,7 +1297,7 @@ func TestCredentialRenewalJob_NextRunIn(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, @@ -1535,7 +1536,7 @@ func TestCredentialRevocationJob_RunLimits(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, @@ -1646,7 +1647,7 @@ func TestCredentialRevocationJob_Run(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, HostId: h.GetPublicId(), @@ -1736,7 +1737,7 @@ func TestCredentialRevocationJob_RunDeleted(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, HostId: h.GetPublicId(), @@ -2114,7 +2115,7 @@ func TestCredentialCleanupJob_Run(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) sess1 := session.TestSession(t, conn, wrapper, session.ComposedOf{ UserId: uId, HostId: h.GetPublicId(), diff --git a/internal/credential/vault/repository_credentials_test.go b/internal/credential/vault/repository_credentials_test.go index 8c0604d02..20f9d1cc5 100644 --- a/internal/credential/vault/repository_credentials_test.go +++ b/internal/credential/vault/repository_credentials_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -103,7 +104,7 @@ func TestRepository_IssueCredentials(t *testing.T) { h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) rc2dc := func(rcs []credential.Request) []*session.DynamicCredential { var dcs []*session.DynamicCredential @@ -221,7 +222,7 @@ func TestRepository_Revoke(t *testing.T) { h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) const ( @@ -339,7 +340,7 @@ func Test_TerminateSession(t *testing.T) { h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) const ( diff --git a/internal/credential/vault/testing_external_test.go b/internal/credential/vault/testing_external_test.go index ef7c17f45..4b24273eb 100644 --- a/internal/credential/vault/testing_external_test.go +++ b/internal/credential/vault/testing_external_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,7 +35,7 @@ func Test_TestCredentials(t *testing.T) { h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) diff --git a/internal/db/clause.go b/internal/db/clause.go new file mode 100644 index 000000000..0beb4d758 --- /dev/null +++ b/internal/db/clause.go @@ -0,0 +1,123 @@ +package db + +import ( + "sort" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +// ColumnValue defines a column and it's assigned value for a database operation +type ColumnValue struct { + column string + value interface{} +} + +type assigner interface { + toAssignment(column string) clause.Assignment +} + +// column represents a table column +type column struct { + name string + table string +} + +func (c *column) toAssignment(column string) clause.Assignment { + return clause.Assignment{ + Column: clause.Column{Name: column}, + Value: clause.Column{Table: c.table, Name: c.name}, + } +} + +func rawAssignment(column string, value interface{}) clause.Assignment { + return clause.Assignment{ + Column: clause.Column{Name: column}, + Value: value, + } +} + +// ExprValue encapsulates an expression value for a column assignment. See +// Expr(...) to create these values. +type ExprValue struct { + sql string + vars []interface{} +} + +func (ev *ExprValue) toAssignment(column string) clause.Assignment { + return clause.Assignment{ + Column: clause.Column{Name: column}, + Value: gorm.Expr(ev.sql, ev.vars...), + } +} + +// Expr creates an expression value (ExprValue) which can be used when setting +// column values for database operations. See: Expr(...) +// +// Set name column to null example: +// SetColumnValues(map[string]interface{}{"name": Expr("NULL")}) +// +// Set exp_time column to N seconds from now: +// SetColumnValues(map[string]interface{}{"exp_time": Expr("wt_add_seconds_to_now(?)", 10)}) +func Expr(expr string, args ...interface{}) ExprValue { + return ExprValue{sql: expr, vars: args} +} + +// SetColumnValues defines a map from column names to values +func SetColumnValues(columnValues map[string]interface{}) []ColumnValue { + keys := make([]string, 0, len(columnValues)) + for key := range columnValues { + keys = append(keys, key) + } + sort.Strings(keys) + + assignments := make([]ColumnValue, len(keys)) + for idx, key := range keys { + assignments[idx] = ColumnValue{column: key, value: columnValues[key]} + } + return assignments +} + +// SetColumns defines a list of column (names) to update using the set of +// proposed insert columns during an on conflict update. +func SetColumns(names []string) []ColumnValue { + assignments := make([]ColumnValue, len(names)) + for idx, name := range names { + assignments[idx] = ColumnValue{ + column: name, + value: column{name: name, table: "excluded"}, + } + } + return assignments +} + +// OnConflict specifies how to handle alternative actions to take when an insert +// results in a unique constraint or exclusion constraint error. +type OnConflict struct { + + // Target specifies what conflict you want to define a policy for. This can + // be any one of these: + // Columns: the name of a specific column or columns + // Constraint: the name of a unique constraint + Target interface{} + + // Action specifies the action to take on conflict. This can be any one of + // these: + // DoNothing: leaves the conflicting record as-is + // UpdateAll: updates all the columns of the conflicting record using the resource's data + // []ColumnValue: update a set of columns of the conflicting record using the set of assignments + Action interface{} +} + +// Constraint defines database constraint name +type Constraint string + +// Columns defines a set of column names +type Columns []string + +// DoNothing defines an "on conflict" action of doing nothing +type DoNothing bool + +// UpdateAll defines an "on conflict" action of updating all columns using the +// proposed insert column values +type UpdateAll bool diff --git a/internal/db/option.go b/internal/db/option.go index 32b50dcc4..ba963875d 100644 --- a/internal/db/option.go +++ b/internal/db/option.go @@ -47,6 +47,9 @@ type Options struct { withGormFormatter hclog.Logger withMaxOpenConnections int + + withOnConflict *OnConflict + withRowsAffected *int64 } type oplogOpts struct { @@ -185,3 +188,19 @@ func WithMaxOpenConnections(max int) Option { o.withMaxOpenConnections = max } } + +// WithOnConflict specifies an optional on conflict criteria which specify +// alternative actions to take when an insert results in a unique constraint or +// exclusion constraint error +func WithOnConflict(onConflict *OnConflict) Option { + return func(o *Options) { + o.withOnConflict = onConflict + } +} + +// WithReturnRowsAffected specifies an option for returning the rows affected +func WithReturnRowsAffected(rowsAffected *int64) Option { + return func(o *Options) { + o.withRowsAffected = rowsAffected + } +} diff --git a/internal/db/option_test.go b/internal/db/option_test.go index 963b9d008..f2cf9588b 100644 --- a/internal/db/option_test.go +++ b/internal/db/option_test.go @@ -141,8 +141,6 @@ func Test_getOpts(t *testing.T) { testOpts := getDefaultOptions() testOpts.withSkipVetForWrite = false assert.Equal(opts, testOpts) - - // try setting to false opts = GetOpts(WithSkipVetForWrite(true)) testOpts = getDefaultOptions() testOpts.withSkipVetForWrite = true @@ -156,8 +154,6 @@ func Test_getOpts(t *testing.T) { testOpts.withWhereClause = "" testOpts.withWhereClauseArgs = nil assert.Equal(opts, testOpts) - - // try setting to false opts = GetOpts(WithWhere("id = ? and foo = ?", 1234, "bar")) testOpts.withWhereClause = "id = ? and foo = ?" testOpts.withWhereClauseArgs = []interface{}{1234, "bar"} @@ -170,8 +166,6 @@ func Test_getOpts(t *testing.T) { testOpts := getDefaultOptions() testOpts.withOrder = "" assert.Equal(opts, testOpts) - - // try setting to false opts = GetOpts(WithOrder("version desc")) testOpts.withOrder = "version desc" assert.Equal(opts, testOpts) @@ -184,8 +178,6 @@ func Test_getOpts(t *testing.T) { assert.Equal(opts, testOpts) testLogger := hclog.New(&hclog.LoggerOptions{}) - - // try setting to false opts = GetOpts(WithGormFormatter(testLogger)) testOpts.withGormFormatter = testLogger assert.Equal(opts, testOpts) @@ -196,10 +188,37 @@ func Test_getOpts(t *testing.T) { opts := GetOpts() testOpts := getDefaultOptions() assert.Equal(opts, testOpts) - - // try setting to false opts = GetOpts(WithMaxOpenConnections(22)) testOpts.withMaxOpenConnections = 22 assert.Equal(opts, testOpts) }) + t.Run("WithOnConflict", func(t *testing.T) { + assert := assert.New(t) + // test default of false + opts := GetOpts() + testOpts := getDefaultOptions() + assert.Equal(opts, testOpts) + + columns := SetColumns([]string{"name", "description"}) + columnValues := SetColumnValues(map[string]interface{}{"expiration": "NULL"}) + testOnConflict := OnConflict{ + Target: Constraint("uniq-name"), + Action: append(columns, columnValues...), + } + opts = GetOpts(WithOnConflict(&testOnConflict)) + testOpts.withOnConflict = &testOnConflict + assert.Equal(opts, testOpts) + }) + t.Run("WithReturnRowsAffected", func(t *testing.T) { + assert := assert.New(t) + // test default of false + opts := GetOpts() + testOpts := getDefaultOptions() + assert.Equal(opts, testOpts) + + var rowsAffected int64 + opts = GetOpts(WithReturnRowsAffected(&rowsAffected)) + testOpts.withRowsAffected = &rowsAffected + assert.Equal(opts, testOpts) + }) } diff --git a/internal/db/read_writer.go b/internal/db/read_writer.go index 119bb8f42..e942147ae 100644 --- a/internal/db/read_writer.go +++ b/internal/db/read_writer.go @@ -15,6 +15,7 @@ import ( wrapping "github.com/hashicorp/go-kms-wrapping" "google.golang.org/protobuf/proto" "gorm.io/gorm" + "gorm.io/gorm/clause" ) const ( @@ -260,10 +261,12 @@ func (rw *Db) lookupAfterWrite(ctx context.Context, i interface{}, opt ...Option return nil } -// Create an object in the db with options: WithOplog, NewOplogMsg and -// WithLookup. WithOplog will write an oplog entry for the create. +// Create an object in the db with options: WithOplog, NewOplogMsg, WithLookup +// and OnConflict. WithOplog will write an oplog entry for the create. // NewOplogMsg will return in-memory oplog message. WithOplog and NewOplogMsg // cannot be used together. WithLookup with to force a lookup after create. +// OnConflict specifies alternative actions to take when an insert results in a +// unique constraint or exclusion constraint error. func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error { const op = "db.Create" if rw.underlying == nil { @@ -295,6 +298,54 @@ func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error { } } } + + db := rw.underlying.WithContext(ctx) + var onConflictDoNothing bool + if opts.withOnConflict != nil { + c := clause.OnConflict{} + switch opts.withOnConflict.Target.(type) { + case Constraint: + c.OnConstraint = string(opts.withOnConflict.Target.(Constraint)) + case Columns: + columns := make([]clause.Column, 0, len(opts.withOnConflict.Target.(Columns))) + for _, name := range opts.withOnConflict.Target.(Columns) { + columns = append(columns, clause.Column{Name: name}) + } + c.Columns = columns + default: + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("invalid conflict target: %v", reflect.TypeOf(opts.withOnConflict.Target))) + } + + switch opts.withOnConflict.Action.(type) { + case DoNothing: + c.DoNothing = true + onConflictDoNothing = true + case UpdateAll: + c.UpdateAll = true + case []ColumnValue: + updates := opts.withOnConflict.Action.([]ColumnValue) + set := make(clause.Set, 0, len(updates)) + for _, s := range updates { + // make sure it's not one of the std immutable columns + if contains([]string{"createtime", "publicid"}, strings.ToLower(s.column)) { + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("Cannot do update on conflict for column %s", s.column)) + } + switch sv := s.value.(type) { + case column: + set = append(set, sv.toAssignment(s.column)) + case ExprValue: + set = append(set, sv.toAssignment(s.column)) + default: + set = append(set, rawAssignment(s.column, s.value)) + } + } + c.DoUpdates = set + default: + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("invalid conflict action: %v", reflect.TypeOf(opts.withOnConflict.Action))) + } + db = db.Clauses(c) + } + var ticket *store.Ticket if withOplog { var err error @@ -303,20 +354,32 @@ func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) } } - if err := rw.underlying.Create(i).Error; err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("create failed"), errors.WithoutEvent()) + tx := db.Create(i) + if tx.Error != nil { + return errors.Wrap(ctx, tx.Error, op, errors.WithMsg("create failed"), errors.WithoutEvent()) + } + if opts.withRowsAffected != nil { + *opts.withRowsAffected = tx.RowsAffected } if withOplog { - if err := rw.addOplog(ctx, CreateOp, opts, ticket, i); err != nil { - return errors.Wrap(ctx, err, op) + switch { + case onConflictDoNothing && tx.RowsAffected == 0: + default: + if err := rw.addOplog(ctx, CreateOp, opts, ticket, i); err != nil { + return errors.Wrap(ctx, err, op) + } } } if opts.newOplogMsg != nil { - msg, err := rw.newOplogMessage(ctx, CreateOp, i) - if err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("returning oplog failed")) + switch { + case onConflictDoNothing && tx.RowsAffected == 0: + default: + msg, err := rw.newOplogMessage(ctx, CreateOp, i) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("returning oplog failed")) + } + *opts.newOplogMsg = *msg } - *opts.newOplogMsg = *msg } if err := rw.lookupAfterWrite(ctx, i, opt...); err != nil { return errors.Wrap(ctx, err, op, errors.WithoutEvent()) diff --git a/internal/db/read_writer_ext_test.go b/internal/db/read_writer_ext_test.go new file mode 100644 index 000000000..889ecdaaa --- /dev/null +++ b/internal/db/read_writer_ext_test.go @@ -0,0 +1,152 @@ +package db_test + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/db_test" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestDb_Create_OnConflict(t *testing.T) { + ctx := context.Background() + wrapper := db.TestWrapper(t) + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + + // create initial user for on conflict tests + id, err := db.NewPublicId("test-user") + require.NoError(t, err) + initialUser, err := db_test.NewTestUser() + require.NoError(t, err) + ts := ×tamp.Timestamp{Timestamp: timestamppb.Now()} + initialUser.CreateTime = ts + initialUser.UpdateTime = ts + initialUser.Name = "foo-" + id + err = rw.Create(ctx, initialUser) + require.NoError(t, err) + assert.NotEmpty(t, initialUser.Id) + + tests := []struct { + name string + onConflict db.OnConflict + wantUpdate bool + wantEmail string + wantOplog bool + }{ + { + name: "set-columns", + onConflict: db.OnConflict{ + Target: db.Columns{"public_id"}, + Action: db.SetColumns([]string{"name"}), + }, + wantUpdate: true, + wantOplog: true, + }, + { + name: "set-column-values", + onConflict: db.OnConflict{ + Target: db.Columns{"public_id"}, + Action: db.SetColumnValues(map[string]interface{}{ + "name": db.Expr("md5(?)", "alice eve smith"), + "email": "alice@gmail.com", + "phone_number": db.Expr("NULL"), + }), + }, + wantUpdate: true, + wantEmail: "alice@gmail.com", + wantOplog: true, + }, + { + name: "both-set-columns-and-set-column-values", + onConflict: func() db.OnConflict { + onConflict := db.OnConflict{ + Target: db.Columns{"public_id"}, + } + cv := db.SetColumns([]string{"name"}) + cv = append(cv, + db.SetColumnValues(map[string]interface{}{ + "email": "alice@gmail.com", + "phone_number": db.Expr("NULL"), + })...) + onConflict.Action = cv + return onConflict + }(), + wantUpdate: true, + wantEmail: "alice@gmail.com", + wantOplog: true, + }, + { + name: "update-all", + onConflict: db.OnConflict{ + Target: db.Columns{"public_id"}, + Action: db.UpdateAll(true), + }, + wantUpdate: true, + wantOplog: true, + }, + { + name: "do-nothing", + onConflict: db.OnConflict{ + Target: db.Columns{"public_id"}, + Action: db.DoNothing(true), + }, + wantOplog: false, + }, + { + name: "on-constraint", + onConflict: db.OnConflict{ + Target: db.Constraint("db_test_user_public_id_key"), + Action: db.SetColumns([]string{"name"}), + }, + wantUpdate: true, + wantOplog: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conflictUser, err := db_test.NewTestUser() + require.NoError(err) + userNameId, err := db.NewPublicId("test-user-name") + require.NoError(err) + conflictUser.PublicId = initialUser.PublicId + conflictUser.Name = userNameId + md := oplog.Metadata{ + "resource-public-id": []string{conflictUser.PublicId}, + "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, + } + var rowsAffected int64 + err = rw.Create(ctx, conflictUser, db.WithOnConflict(&tt.onConflict), db.WithOplog(wrapper, md), db.WithReturnRowsAffected(&rowsAffected)) + require.NoError(err) + if tt.wantOplog { + err = db.TestVerifyOplog(t, rw, conflictUser.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + assert.NoError(err) + } + foundUser, err := db_test.NewTestUser() + require.NoError(err) + foundUser.PublicId = conflictUser.PublicId + err = rw.LookupByPublicId(context.Background(), foundUser) + require.NoError(err) + if tt.wantUpdate { + assert.Equal(int64(1), rowsAffected) + assert.Equal(conflictUser.Id, foundUser.Id) + assert.Equal(conflictUser.Name, foundUser.Name) + if tt.wantEmail != "" { + assert.Equal(tt.wantEmail, foundUser.Email) + } + } else { + assert.Equal(int64(0), rowsAffected) + assert.NotEqual(conflictUser.Id, foundUser.Id) + assert.NotEqual(conflictUser.Name, foundUser.Name) + } + }) + } +} diff --git a/internal/db/read_writer_test.go b/internal/db/read_writer_test.go index 14404793c..793663dd4 100644 --- a/internal/db/read_writer_test.go +++ b/internal/db/read_writer_test.go @@ -723,8 +723,8 @@ func TestDb_Create(t *testing.T) { metadata := oplog.Metadata{ "resource-public-id": []string{user.PublicId}, - // "op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()}, } + err = w.WriteOplogEntryWith(context.Background(), TestWrapper(t), ticket, metadata, []*oplog.Message{&createMsg, &updateMsg}) require.NoError(err) diff --git a/internal/db/schema/migrations/oss/postgres/17/01_servers.up.sql b/internal/db/schema/migrations/oss/postgres/17/01_servers.up.sql new file mode 100644 index 000000000..4b84e5c9c --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/17/01_servers.up.sql @@ -0,0 +1,16 @@ +begin; + +-- server table inserts/updates historically included setting the update_time +-- column to now(). Since we're now using the "standard" db package to do this, +-- we need to add triggers to set the update_time for every insert/update. +create trigger + update_time_column +before update on server + for each row execute procedure update_time_column(); + +create trigger + insert_time_column +before insert on server + for each row execute procedure update_time_column(); + +commit; \ No newline at end of file diff --git a/internal/db/schema/migrations/oss/postgres_14_01_test.go b/internal/db/schema/migrations/oss/postgres_14_01_test.go index 3483a078a..fcc5421b2 100644 --- a/internal/db/schema/migrations/oss/postgres_14_01_test.go +++ b/internal/db/schema/migrations/oss/postgres_14_01_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/testing/dbtest" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/stretchr/testify/assert" @@ -79,7 +80,7 @@ func TestMigrations_UserDimension(t *testing.T) { h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) var sessions []*session.Session kmsCache := kms.TestKms(t, conn, wrapper) diff --git a/internal/db/schema/migrations/oss/postgres_16_05_test.go b/internal/db/schema/migrations/oss/postgres_16_05_test.go index f2d960738..58cbda927 100644 --- a/internal/db/schema/migrations/oss/postgres_16_05_test.go +++ b/internal/db/schema/migrations/oss/postgres_16_05_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/testing/dbtest" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/stretchr/testify/assert" @@ -81,7 +82,7 @@ func TestMigrations_CredentialDimension(t *testing.T) { h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) var sessions []*session.Session kmsCache := kms.TestKms(t, conn, wrapper) @@ -157,12 +158,12 @@ func TestMigrations_CredentialDimension(t *testing.T) { require.Equal(want, state) } -func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapper, tar *target.TcpTarget) []*session.DynamicCredential { +func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapper, tar target.Target) []*session.DynamicCredential { t.Helper() rw := db.New(conn) ctx := context.Background() - stores := vault.TestCredentialStores(t, conn, wrapper, tar.ScopeId, 1) + stores := vault.TestCredentialStores(t, conn, wrapper, tar.GetScopeId(), 1) libs := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) targetRepo, err := target.NewRepository(rw, rw, kms) diff --git a/internal/errors/code.go b/internal/errors/code.go index bc4c254cf..02ea814ec 100644 --- a/internal/errors/code.go +++ b/internal/errors/code.go @@ -66,9 +66,11 @@ const ( GenKey Code = 304 // GenKey represents an error occurred during the underlying key generation process GenCert Code = 305 // GenCert represents an error occurred during the underlying certificate generation process - // General system errors are reserved Codes 400-599 and align with http client and server error codes - Forbidden Code = 403 // Forbidden represents the operation is forbidden - Internal Code = 500 // InternalError represents the system encountered an unexpected condition. + // General system errors are reserved Codes 400-599 and align with http + // client and server error codes + Unauthorized Code = 401 // Unauthorized represents the operation is unauthorized + Forbidden Code = 403 // Forbidden represents the operation is forbidden + Internal Code = 500 // InternalError represents the system encountered an unexpected condition. // DB errors are reserved Codes from 1000-1999 CheckConstraint Code = 1000 // CheckConstraint represents a check constraint error diff --git a/internal/errors/error.go b/internal/errors/error.go index c729f9e80..a4fdf3f99 100644 --- a/internal/errors/error.go +++ b/internal/errors/error.go @@ -7,6 +7,7 @@ import ( "runtime" "strings" + pberrors "github.com/hashicorp/boundary/internal/gen/errors" "github.com/hashicorp/boundary/internal/observability/event" "github.com/jackc/pgconn" ) @@ -266,6 +267,49 @@ func (e *Err) Error() string { return s.String() } +// ToPbErrors will convert to an Err protobuf +func ToPbErrors(err *Err) *pberrors.Err { + pbErr := &pberrors.Err{ + Code: uint32(err.Code), + Msg: err.Msg, + Op: string(err.Op), + } + + var wrappedErr *Err + isWrappedErr := As(err.Wrapped, &wrappedErr) + switch { + case err.Wrapped == nil: + pbErr.Wrapped = &pberrors.Err_None{ + None: false, + } + case isWrappedErr: + pbErr.Wrapped = &pberrors.Err_Err{ + Err: ToPbErrors(wrappedErr), + } + default: + pbErr.Wrapped = &pberrors.Err_StdError{ + StdError: err.Wrapped.Error(), + } + } + return pbErr +} + +// FromPbErrors will convert from Err protobuf +func FromPbErrors(pbErr *pberrors.Err) *Err { + err := &Err{ + Code: Code(pbErr.Code), + Msg: pbErr.Msg, + Op: Op(pbErr.Op), + } + switch w := pbErr.Wrapped.(type) { + case *pberrors.Err_Err: + err.Wrapped = FromPbErrors(w.Err) + case *pberrors.Err_StdError: + err.Wrapped = errors.New(w.StdError) + } + return err +} + func join(str *strings.Builder, delim string, s string) { if str.Len() == 0 { _, _ = str.WriteString(s) diff --git a/internal/errors/info.go b/internal/errors/info.go index 3d832623a..c9ec7a77c 100644 --- a/internal/errors/info.go +++ b/internal/errors/info.go @@ -148,6 +148,10 @@ var errorCodeInfo = map[Code]Info{ Message: "forbidden", Kind: Other, }, + Unauthorized: { + Message: "unauthorized", + Kind: Other, + }, CheckConstraint: { Message: "constraint check failed", Kind: Integrity, diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 1f3125ceb..8b5f11ef0 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -5038,7 +5038,8 @@ "items": { "type": "string" }, - "description": "The IDs of the Host Sets associated with this Target." + "description": "Output only. The IDs of the Host Sets associated with this Target.", + "readOnly": true }, "host_sets": { "type": "array", @@ -5053,7 +5054,8 @@ "items": { "type": "string" }, - "description": "The IDs of the Host Sources associated with this Target." + "description": "Output only. The IDs of the Host Sources associated with this Target.", + "readOnly": true }, "host_sources": { "type": "array", @@ -5082,7 +5084,8 @@ "items": { "type": "string" }, - "description": "The IDs of the application credential library ids associated with this Target. Deprecated: use application_credential_source_ids instead." + "description": "Output only. The IDs of the application credential library ids associated with this Target. Deprecated: use application_credential_source_ids instead.", + "readOnly": true }, "application_credential_libraries": { "type": "array", @@ -5097,7 +5100,8 @@ "items": { "type": "string" }, - "description": "The IDs of the application credential source ids associated with this Target." + "description": "Output only. The IDs of the application credential source ids associated with this Target.", + "readOnly": true }, "application_credential_sources": { "type": "array", diff --git a/internal/gen/controller/api/empty_msg.pb.go b/internal/gen/controller/api/empty_msg.pb.go new file mode 100644 index 000000000..340e4d468 --- /dev/null +++ b/internal/gen/controller/api/empty_msg.pb.go @@ -0,0 +1,137 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/api/v1/empty_msg.proto + +package api + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// EmptyResponse represents an empty response +type EmptyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *EmptyResponse) Reset() { + *x = EmptyResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_v1_empty_msg_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EmptyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EmptyResponse) ProtoMessage() {} + +func (x *EmptyResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_v1_empty_msg_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead. +func (*EmptyResponse) Descriptor() ([]byte, []int) { + return file_controller_api_v1_empty_msg_proto_rawDescGZIP(), []int{0} +} + +var File_controller_api_v1_empty_msg_proto protoreflect.FileDescriptor + +var file_controller_api_v1_empty_msg_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x22, 0x0f, 0x0a, 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_v1_empty_msg_proto_rawDescOnce sync.Once + file_controller_api_v1_empty_msg_proto_rawDescData = file_controller_api_v1_empty_msg_proto_rawDesc +) + +func file_controller_api_v1_empty_msg_proto_rawDescGZIP() []byte { + file_controller_api_v1_empty_msg_proto_rawDescOnce.Do(func() { + file_controller_api_v1_empty_msg_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_v1_empty_msg_proto_rawDescData) + }) + return file_controller_api_v1_empty_msg_proto_rawDescData +} + +var file_controller_api_v1_empty_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_api_v1_empty_msg_proto_goTypes = []interface{}{ + (*EmptyResponse)(nil), // 0: controller.api.v1.EmptyResponse +} +var file_controller_api_v1_empty_msg_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_controller_api_v1_empty_msg_proto_init() } +func file_controller_api_v1_empty_msg_proto_init() { + if File_controller_api_v1_empty_msg_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_v1_empty_msg_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EmptyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_v1_empty_msg_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_api_v1_empty_msg_proto_goTypes, + DependencyIndexes: file_controller_api_v1_empty_msg_proto_depIdxs, + MessageInfos: file_controller_api_v1_empty_msg_proto_msgTypes, + }.Build() + File_controller_api_v1_empty_msg_proto = out.File + file_controller_api_v1_empty_msg_proto_rawDesc = nil + file_controller_api_v1_empty_msg_proto_goTypes = nil + file_controller_api_v1_empty_msg_proto_depIdxs = nil +} diff --git a/internal/gen/controller/auth/auth.pb.go b/internal/gen/controller/auth/auth.pb.go new file mode 100644 index 000000000..103a2360a --- /dev/null +++ b/internal/gen/controller/auth/auth.pb.go @@ -0,0 +1,263 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/auth/v1/auth.proto + +package auth + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// RequestInfo contains request parameters necessary for checking authn/authz +type RequestInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // path of the request + Path string `protobuf:"bytes,10,opt,name=path,proto3" json:"path,omitempty"` + // method (http verb) of the request + Method string `protobuf:"bytes,20,opt,name=method,proto3" json:"method,omitempty"` + // public_id for the request + PublicId string `protobuf:"bytes,30,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty"` + // encrypted_token from the request + EncryptedToken string `protobuf:"bytes,40,opt,name=encrypted_token,json=encryptedToken,proto3" json:"encrypted_token,omitempty"` + // token from the request + Token string `protobuf:"bytes,50,opt,name=token,proto3" json:"token,omitempty"` + // token_format of the request's token + TokenFormat uint32 `protobuf:"varint,60,opt,name=token_format,json=tokenFormat,proto3" json:"token_format,omitempty"` + // scope_id_override for the request (helpful for tests) + ScopeIdOverride string `protobuf:"bytes,70,opt,name=scope_id_override,json=scopeIdOverride,proto3" json:"scope_id_override,omitempty"` + // user_id_override for the request (helpful for tests) + UserIdOverride string `protobuf:"bytes,80,opt,name=user_id_override,json=userIdOverride,proto3" json:"user_id_override,omitempty"` + // disable_authz_failures for the request (helpful for tests) + DisableAuthzFailures bool `protobuf:"varint,90,opt,name=disable_authz_failures,json=disableAuthzFailures,proto3" json:"disable_authz_failures,omitempty"` + // disable_auth_entirely for the request (helpful for tests) + DisableAuthEntirely bool `protobuf:"varint,100,opt,name=disable_auth_entirely,json=disableAuthEntirely,proto3" json:"disable_auth_entirely,omitempty"` + // ticket is a unique id that allows the grpc-gateway to verify that the info + // came from its companion http proxy + Ticket string `protobuf:"bytes,110,opt,name=ticket,proto3" json:"ticket,omitempty"` +} + +func (x *RequestInfo) Reset() { + *x = RequestInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_auth_v1_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestInfo) ProtoMessage() {} + +func (x *RequestInfo) ProtoReflect() protoreflect.Message { + mi := &file_controller_auth_v1_auth_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestInfo.ProtoReflect.Descriptor instead. +func (*RequestInfo) Descriptor() ([]byte, []int) { + return file_controller_auth_v1_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *RequestInfo) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *RequestInfo) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *RequestInfo) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *RequestInfo) GetEncryptedToken() string { + if x != nil { + return x.EncryptedToken + } + return "" +} + +func (x *RequestInfo) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *RequestInfo) GetTokenFormat() uint32 { + if x != nil { + return x.TokenFormat + } + return 0 +} + +func (x *RequestInfo) GetScopeIdOverride() string { + if x != nil { + return x.ScopeIdOverride + } + return "" +} + +func (x *RequestInfo) GetUserIdOverride() string { + if x != nil { + return x.UserIdOverride + } + return "" +} + +func (x *RequestInfo) GetDisableAuthzFailures() bool { + if x != nil { + return x.DisableAuthzFailures + } + return false +} + +func (x *RequestInfo) GetDisableAuthEntirely() bool { + if x != nil { + return x.DisableAuthEntirely + } + return false +} + +func (x *RequestInfo) GetTicket() string { + if x != nil { + return x.Ticket + } + return "" +} + +var File_controller_auth_v1_auth_proto protoreflect.FileDescriptor + +var file_controller_auth_v1_auth_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x75, 0x74, + 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x12, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x22, 0x90, 0x03, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, + 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x32, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x3c, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x2a, + 0x0a, 0x11, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, + 0x69, 0x64, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x49, 0x64, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x50, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4f, 0x76, 0x65, 0x72, + 0x72, 0x69, 0x64, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x61, 0x75, 0x74, 0x68, 0x7a, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x5a, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, + 0x68, 0x7a, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x72, + 0x65, 0x6c, 0x79, 0x18, 0x64, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x6c, 0x79, 0x12, 0x16, + 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x42, 0x41, 0x5a, 0x3f, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, + 0x61, 0x75, 0x74, 0x68, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_controller_auth_v1_auth_proto_rawDescOnce sync.Once + file_controller_auth_v1_auth_proto_rawDescData = file_controller_auth_v1_auth_proto_rawDesc +) + +func file_controller_auth_v1_auth_proto_rawDescGZIP() []byte { + file_controller_auth_v1_auth_proto_rawDescOnce.Do(func() { + file_controller_auth_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_auth_v1_auth_proto_rawDescData) + }) + return file_controller_auth_v1_auth_proto_rawDescData +} + +var file_controller_auth_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_auth_v1_auth_proto_goTypes = []interface{}{ + (*RequestInfo)(nil), // 0: controller.auth.v1.RequestInfo +} +var file_controller_auth_v1_auth_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_controller_auth_v1_auth_proto_init() } +func file_controller_auth_v1_auth_proto_init() { + if File_controller_auth_v1_auth_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_auth_v1_auth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_auth_v1_auth_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_auth_v1_auth_proto_goTypes, + DependencyIndexes: file_controller_auth_v1_auth_proto_depIdxs, + MessageInfos: file_controller_auth_v1_auth_proto_msgTypes, + }.Build() + File_controller_auth_v1_auth_proto = out.File + file_controller_auth_v1_auth_proto_rawDesc = nil + file_controller_auth_v1_auth_proto_goTypes = nil + file_controller_auth_v1_auth_proto_depIdxs = nil +} diff --git a/internal/gen/errors/errors.pb.go b/internal/gen/errors/errors.pb.go new file mode 100644 index 000000000..80d525d88 --- /dev/null +++ b/internal/gen/errors/errors.pb.go @@ -0,0 +1,322 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: errors/v1/errors.proto + +package errors + +import ( + api "github.com/hashicorp/boundary/internal/gen/controller/api" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Err is a protobuf representation of a domain error +type Err struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // code is the error's code, which can be used to get the error's + // errorCodeInfo, which contains the error's Kind and Message + Code uint32 `protobuf:"varint,10,opt,name=code,proto3" json:"code,omitempty"` + // msg for the error + Msg string `protobuf:"bytes,20,opt,name=msg,proto3" json:"msg,omitempty"` + // op represents the operation raising/propagating an error and is optional. + Op string `protobuf:"bytes,30,opt,name=op,proto3" json:"op,omitempty"` + // wrapped represents an optionally wrapped error + // + // Types that are assignable to Wrapped: + // *Err_Err + // *Err_StdError + // *Err_None + Wrapped isErr_Wrapped `protobuf_oneof:"wrapped"` +} + +func (x *Err) Reset() { + *x = Err{} + if protoimpl.UnsafeEnabled { + mi := &file_errors_v1_errors_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Err) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Err) ProtoMessage() {} + +func (x *Err) ProtoReflect() protoreflect.Message { + mi := &file_errors_v1_errors_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Err.ProtoReflect.Descriptor instead. +func (*Err) Descriptor() ([]byte, []int) { + return file_errors_v1_errors_proto_rawDescGZIP(), []int{0} +} + +func (x *Err) GetCode() uint32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *Err) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +func (x *Err) GetOp() string { + if x != nil { + return x.Op + } + return "" +} + +func (m *Err) GetWrapped() isErr_Wrapped { + if m != nil { + return m.Wrapped + } + return nil +} + +func (x *Err) GetErr() *Err { + if x, ok := x.GetWrapped().(*Err_Err); ok { + return x.Err + } + return nil +} + +func (x *Err) GetStdError() string { + if x, ok := x.GetWrapped().(*Err_StdError); ok { + return x.StdError + } + return "" +} + +func (x *Err) GetNone() bool { + if x, ok := x.GetWrapped().(*Err_None); ok { + return x.None + } + return false +} + +type isErr_Wrapped interface { + isErr_Wrapped() +} + +type Err_Err struct { + // err is a wrapped Err (aka a domain error) + Err *Err `protobuf:"bytes,40,opt,name=err,proto3,oneof"` +} + +type Err_StdError struct { + // std_error is a wrapped std error + StdError string `protobuf:"bytes,50,opt,name=std_error,json=stdError,proto3,oneof"` +} + +type Err_None struct { + // no error or err is wrapped + None bool `protobuf:"varint,60,opt,name=none,proto3,oneof"` +} + +func (*Err_Err) isErr_Wrapped() {} + +func (*Err_StdError) isErr_Wrapped() {} + +func (*Err_None) isErr_Wrapped() {} + +// ApiError is a protobuf representation of a controller ApiError +type ApiError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // api_error is the controller ApiError + ApiError *api.Error `protobuf:"bytes,10,opt,name=api_error,json=apiError,proto3" json:"api_error,omitempty"` + // status is the status code assocatiated with the api error + Status int32 `protobuf:"varint,20,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *ApiError) Reset() { + *x = ApiError{} + if protoimpl.UnsafeEnabled { + mi := &file_errors_v1_errors_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApiError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApiError) ProtoMessage() {} + +func (x *ApiError) ProtoReflect() protoreflect.Message { + mi := &file_errors_v1_errors_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApiError.ProtoReflect.Descriptor instead. +func (*ApiError) Descriptor() ([]byte, []int) { + return file_errors_v1_errors_proto_rawDescGZIP(), []int{1} +} + +func (x *ApiError) GetApiError() *api.Error { + if x != nil { + return x.ApiError + } + return nil +} + +func (x *ApiError) GetStatus() int32 { + if x != nil { + return x.Status + } + return 0 +} + +var File_errors_v1_errors_proto protoreflect.FileDescriptor + +var file_errors_v1_errors_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, + 0x2e, 0x76, 0x31, 0x1a, 0x1d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x9f, 0x01, 0x0a, 0x03, 0x45, 0x72, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, + 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, + 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x70, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x70, + 0x12, 0x22, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x48, 0x00, 0x52, + 0x03, 0x65, 0x72, 0x72, 0x12, 0x1d, 0x0a, 0x09, 0x73, 0x74, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x73, 0x74, 0x64, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, + 0x08, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x77, 0x72, 0x61, + 0x70, 0x70, 0x65, 0x64, 0x22, 0x59, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x35, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x08, 0x61, + 0x70, 0x69, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, + 0x3a, 0x5a, 0x38, 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, 0x67, 0x65, 0x6e, 0x2f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_errors_v1_errors_proto_rawDescOnce sync.Once + file_errors_v1_errors_proto_rawDescData = file_errors_v1_errors_proto_rawDesc +) + +func file_errors_v1_errors_proto_rawDescGZIP() []byte { + file_errors_v1_errors_proto_rawDescOnce.Do(func() { + file_errors_v1_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_v1_errors_proto_rawDescData) + }) + return file_errors_v1_errors_proto_rawDescData +} + +var file_errors_v1_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_errors_v1_errors_proto_goTypes = []interface{}{ + (*Err)(nil), // 0: errors.v1.Err + (*ApiError)(nil), // 1: errors.v1.ApiError + (*api.Error)(nil), // 2: controller.api.v1.Error +} +var file_errors_v1_errors_proto_depIdxs = []int32{ + 0, // 0: errors.v1.Err.err:type_name -> errors.v1.Err + 2, // 1: errors.v1.ApiError.api_error:type_name -> controller.api.v1.Error + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_errors_v1_errors_proto_init() } +func file_errors_v1_errors_proto_init() { + if File_errors_v1_errors_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_errors_v1_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Err); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_errors_v1_errors_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApiError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_errors_v1_errors_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Err_Err)(nil), + (*Err_StdError)(nil), + (*Err_None)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_errors_v1_errors_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_errors_v1_errors_proto_goTypes, + DependencyIndexes: file_errors_v1_errors_proto_depIdxs, + MessageInfos: file_errors_v1_errors_proto_msgTypes, + }.Build() + File_errors_v1_errors_proto = out.File + file_errors_v1_errors_proto_rawDesc = nil + file_errors_v1_errors_proto_goTypes = nil + file_errors_v1_errors_proto_depIdxs = nil +} diff --git a/internal/gen/testing/interceptor/greeter.pb.go b/internal/gen/testing/interceptor/greeter.pb.go new file mode 100644 index 000000000..a4b9df67f --- /dev/null +++ b/internal/gen/testing/interceptor/greeter.pb.go @@ -0,0 +1,222 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: testing/interceptor/v1/greeter.proto + +package interceptor + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The request message containing the user's name. +type SayHelloRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *SayHelloRequest) Reset() { + *x = SayHelloRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayHelloRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayHelloRequest) ProtoMessage() {} + +func (x *SayHelloRequest) ProtoReflect() protoreflect.Message { + mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayHelloRequest.ProtoReflect.Descriptor instead. +func (*SayHelloRequest) Descriptor() ([]byte, []int) { + return file_testing_interceptor_v1_greeter_proto_rawDescGZIP(), []int{0} +} + +func (x *SayHelloRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// The response message containing the greetings +type SayHelloResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *SayHelloResponse) Reset() { + *x = SayHelloResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayHelloResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayHelloResponse) ProtoMessage() {} + +func (x *SayHelloResponse) ProtoReflect() protoreflect.Message { + mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayHelloResponse.ProtoReflect.Descriptor instead. +func (*SayHelloResponse) Descriptor() ([]byte, []int) { + return file_testing_interceptor_v1_greeter_proto_rawDescGZIP(), []int{1} +} + +func (x *SayHelloResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_testing_interceptor_v1_greeter_proto protoreflect.FileDescriptor + +var file_testing_interceptor_v1_greeter_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, + 0x65, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x22, 0x25, + 0x0a, 0x0f, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2c, 0x0a, 0x10, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x32, 0x71, 0x0a, 0x0e, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, + 0x6f, 0x12, 0x27, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x79, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x74, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x4c, 0x5a, 0x4a, 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, 0x67, 0x65, 0x6e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x3b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, + 0x70, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_testing_interceptor_v1_greeter_proto_rawDescOnce sync.Once + file_testing_interceptor_v1_greeter_proto_rawDescData = file_testing_interceptor_v1_greeter_proto_rawDesc +) + +func file_testing_interceptor_v1_greeter_proto_rawDescGZIP() []byte { + file_testing_interceptor_v1_greeter_proto_rawDescOnce.Do(func() { + file_testing_interceptor_v1_greeter_proto_rawDescData = protoimpl.X.CompressGZIP(file_testing_interceptor_v1_greeter_proto_rawDescData) + }) + return file_testing_interceptor_v1_greeter_proto_rawDescData +} + +var file_testing_interceptor_v1_greeter_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_testing_interceptor_v1_greeter_proto_goTypes = []interface{}{ + (*SayHelloRequest)(nil), // 0: testing.interceptor.v1.SayHelloRequest + (*SayHelloResponse)(nil), // 1: testing.interceptor.v1.SayHelloResponse +} +var file_testing_interceptor_v1_greeter_proto_depIdxs = []int32{ + 0, // 0: testing.interceptor.v1.GreeterService.SayHello:input_type -> testing.interceptor.v1.SayHelloRequest + 1, // 1: testing.interceptor.v1.GreeterService.SayHello:output_type -> testing.interceptor.v1.SayHelloResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_testing_interceptor_v1_greeter_proto_init() } +func file_testing_interceptor_v1_greeter_proto_init() { + if File_testing_interceptor_v1_greeter_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_testing_interceptor_v1_greeter_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayHelloRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_testing_interceptor_v1_greeter_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayHelloResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_testing_interceptor_v1_greeter_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_testing_interceptor_v1_greeter_proto_goTypes, + DependencyIndexes: file_testing_interceptor_v1_greeter_proto_depIdxs, + MessageInfos: file_testing_interceptor_v1_greeter_proto_msgTypes, + }.Build() + File_testing_interceptor_v1_greeter_proto = out.File + file_testing_interceptor_v1_greeter_proto_rawDesc = nil + file_testing_interceptor_v1_greeter_proto_goTypes = nil + file_testing_interceptor_v1_greeter_proto_depIdxs = nil +} diff --git a/internal/gen/testing/interceptor/greeter_grpc.pb.go b/internal/gen/testing/interceptor/greeter_grpc.pb.go new file mode 100644 index 000000000..7a4e8ce89 --- /dev/null +++ b/internal/gen/testing/interceptor/greeter_grpc.pb.go @@ -0,0 +1,103 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package interceptor + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GreeterServiceClient is the client API for GreeterService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GreeterServiceClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *SayHelloRequest, opts ...grpc.CallOption) (*SayHelloResponse, error) +} + +type greeterServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewGreeterServiceClient(cc grpc.ClientConnInterface) GreeterServiceClient { + return &greeterServiceClient{cc} +} + +func (c *greeterServiceClient) SayHello(ctx context.Context, in *SayHelloRequest, opts ...grpc.CallOption) (*SayHelloResponse, error) { + out := new(SayHelloResponse) + err := c.cc.Invoke(ctx, "/testing.interceptor.v1.GreeterService/SayHello", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServiceServer is the server API for GreeterService service. +// All implementations must embed UnimplementedGreeterServiceServer +// for forward compatibility +type GreeterServiceServer interface { + // Sends a greeting + SayHello(context.Context, *SayHelloRequest) (*SayHelloResponse, error) + mustEmbedUnimplementedGreeterServiceServer() +} + +// UnimplementedGreeterServiceServer must be embedded to have forward compatible implementations. +type UnimplementedGreeterServiceServer struct { +} + +func (UnimplementedGreeterServiceServer) SayHello(context.Context, *SayHelloRequest) (*SayHelloResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") +} +func (UnimplementedGreeterServiceServer) mustEmbedUnimplementedGreeterServiceServer() {} + +// UnsafeGreeterServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GreeterServiceServer will +// result in compilation errors. +type UnsafeGreeterServiceServer interface { + mustEmbedUnimplementedGreeterServiceServer() +} + +func RegisterGreeterServiceServer(s grpc.ServiceRegistrar, srv GreeterServiceServer) { + s.RegisterService(&GreeterService_ServiceDesc, srv) +} + +func _GreeterService_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SayHelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServiceServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/testing.interceptor.v1.GreeterService/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServiceServer).SayHello(ctx, req.(*SayHelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// GreeterService_ServiceDesc is the grpc.ServiceDesc for GreeterService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GreeterService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "testing.interceptor.v1.GreeterService", + HandlerType: (*GreeterServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _GreeterService_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "testing/interceptor/v1/greeter.proto", +} diff --git a/internal/proto/local/controller/api/resources/targets/v1/target.proto b/internal/proto/local/controller/api/resources/targets/v1/target.proto index 82aaf87c5..368995f3f 100644 --- a/internal/proto/local/controller/api/resources/targets/v1/target.proto +++ b/internal/proto/local/controller/api/resources/targets/v1/target.proto @@ -112,13 +112,13 @@ message Target { // The type of the Target. string type = 90; - // The IDs of the Host Sets associated with this Target. + // Output only. The IDs of the Host Sets associated with this Target. repeated string host_set_ids = 100 [json_name="host_set_ids"]; // Output only. The Host Sets associated with this Target. repeated HostSet host_sets = 110 [json_name="host_sets"]; - // The IDs of the Host Sources associated with this Target. + // Output only. The IDs of the Host Sources associated with this Target. repeated string host_source_ids = 420 [json_name="host_source_ids"]; // Output only. The Host Sources associated with this Target. @@ -133,12 +133,12 @@ message Target { // Optional boolean expression to filter the workers that are allowed to satisfy this request. google.protobuf.StringValue worker_filter = 140 [json_name="worker_filter", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this: "worker_filter" that: "WorkerFilter"}]; - // The IDs of the application credential library ids associated with this Target. Deprecated: use application_credential_source_ids instead. + // Output only. The IDs of the application credential library ids associated with this Target. Deprecated: use application_credential_source_ids instead. repeated string application_credential_library_ids = 150 [json_name="application_credential_library_ids", deprecated = true]; // Output only. The application credential libraries associated with this Target. Deprecated: use application_credential_sources instead. repeated CredentialLibrary application_credential_libraries = 180 [json_name="application_credential_libraries", deprecated = true]; - // The IDs of the application credential source ids associated with this Target. + // Output only. The IDs of the application credential source ids associated with this Target. repeated string application_credential_source_ids = 400 [json_name="application_credential_source_ids"]; // Output only. The application credential sources associated with this Target. repeated CredentialSource application_credential_sources = 410 [json_name="application_credential_sources"]; @@ -238,4 +238,4 @@ message SessionAuthorization { // Output only. The credentials for this session. repeated SessionCredential credentials = 110 [json_name="credentials"]; -} \ No newline at end of file +} diff --git a/internal/proto/local/controller/api/v1/empty_msg.proto b/internal/proto/local/controller/api/v1/empty_msg.proto new file mode 100644 index 000000000..2be840d52 --- /dev/null +++ b/internal/proto/local/controller/api/v1/empty_msg.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package controller.api.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api;api"; + +// EmptyResponse represents an empty response +message EmptyResponse {} diff --git a/internal/proto/local/controller/auth/v1/auth.proto b/internal/proto/local/controller/auth/v1/auth.proto new file mode 100644 index 000000000..840ffa12b --- /dev/null +++ b/internal/proto/local/controller/auth/v1/auth.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package controller.auth.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/auth;auth"; + +// RequestInfo contains request parameters necessary for checking authn/authz +message RequestInfo { + // path of the request + string path = 10; + + // method (http verb) of the request + string method = 20; + + // public_id for the request + string public_id = 30; + + // encrypted_token from the request + string encrypted_token = 40; + + // token from the request + string token = 50; + + // token_format of the request's token + uint32 token_format = 60; + + // scope_id_override for the request (helpful for tests) + string scope_id_override = 70; + + // user_id_override for the request (helpful for tests) + string user_id_override = 80; + + // disable_authz_failures for the request (helpful for tests) + bool disable_authz_failures = 90; + + // disable_auth_entirely for the request (helpful for tests) + bool disable_auth_entirely = 100; + + // ticket is a unique id that allows the grpc-gateway to verify that the info + // came from its companion http proxy + string ticket = 110; +} diff --git a/internal/proto/local/controller/servers/v1/servers.proto b/internal/proto/local/controller/servers/v1/servers.proto index 7adf4bbb3..ecb426497 100644 --- a/internal/proto/local/controller/servers/v1/servers.proto +++ b/internal/proto/local/controller/servers/v1/servers.proto @@ -17,6 +17,7 @@ message Server { // private ID and name to be identical, and there is currently no reason we // can think of to allow a given server's name to match another within the // same cluster. So we simply use the private ID and deprecate this. + // @inject_tag: `gorm:"-"` string name = 30 [deprecated = true]; // Description of the resource diff --git a/internal/proto/local/controller/storage/target/store/v1/target.proto b/internal/proto/local/controller/storage/target/store/v1/target.proto index 837b66ae1..748827bb4 100644 --- a/internal/proto/local/controller/storage/target/store/v1/target.proto +++ b/internal/proto/local/controller/storage/target/store/v1/target.proto @@ -73,70 +73,6 @@ message TargetHostSet { timestamp.v1.Timestamp create_time = 30; } -message TcpTarget { - // public_id is used to access the TargetTcp via an API - // @inject_tag: gorm:"primary_key" - string public_id = 10; - - // scope id for the TargetTcp - // @inject_tag: `gorm:"default:null"` - string scope_id = 20; - - // name is the optional friendly name used to - // access the TargetTcp via an API - // @inject_tag: `gorm:"default:null"` - string name = 30 - [(custom_options.v1.mask_mapping) = { this: "name" that: "name" }]; - - // description of the TargetTcp - // @inject_tag: `gorm:"default:null"` - string description = 40 [(custom_options.v1.mask_mapping) = { - this: "description" - that: "description" - }]; - - // create_time from the RDBMS - // @inject_tag: `gorm:"default:current_timestamp"` - timestamp.v1.Timestamp create_time = 50; - - // update_time from the RDBMS - // @inject_tag: `gorm:"default:current_timestamp"` - timestamp.v1.Timestamp update_time = 60; - - // version allows optimistic locking of the TargetTcp when modifying the - // TargetTcp - // @inject_tag: `gorm:"default:null"` - uint32 version = 70; - - // default port of the TargetTcp - // @inject_tag: `gorm:"default:null"` - uint32 default_port = 80 [(custom_options.v1.mask_mapping) = { - this: "DefaultPort" - that: "attributes.default_port" - }]; - - // Maximum total lifetime of a created session, in seconds - // @inject_tag: `gorm:"default:null"` - uint32 session_max_seconds = 100 [(custom_options.v1.mask_mapping) = { - this: "SessionMaxSeconds" - that: "session_max_seconds" - }]; - - // Maximum number of connections in a session - // @inject_tag: `gorm:"default:null"` - int32 session_connection_limit = 110 [(custom_options.v1.mask_mapping) = { - this: "SessionConnectionLimit" - that: "session_connection_limit" - }]; - - // A boolean expression that allows filtering the workers that can handle a session - // @inject_tag: `gorm:"default:null"` - string worker_filter = 120 [(custom_options.v1.mask_mapping) = { - this: "WorkerFilter" - that: "worker_filter" - }]; -} - message CredentialLibrary { // target_id of the Target // @inject_tag: gorm:"primary_key" diff --git a/internal/proto/local/controller/storage/target/tcp/store/v1/target.proto b/internal/proto/local/controller/storage/target/tcp/store/v1/target.proto new file mode 100644 index 000000000..beffda5f0 --- /dev/null +++ b/internal/proto/local/controller/storage/target/tcp/store/v1/target.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; + +package controller.storage.target.tcp.store.v1; +option go_package = "github.com/hashicorp/boundary/internal/target/tcp/store;store"; + +import "controller/storage/timestamp/v1/timestamp.proto"; +import "controller/custom_options/v1/options.proto"; + +message Target { + // public_id is used to access the tcp.Target via an API + // @inject_tag: gorm:"primary_key" + string public_id = 10; + + // scope id for the tcp.Target + // @inject_tag: `gorm:"default:null"` + string scope_id = 20; + + // name is the optional friendly name used to + // access the tcp.Target via an API + // @inject_tag: `gorm:"default:null"` + string name = 30 + [(custom_options.v1.mask_mapping) = { this: "name" that: "name" }]; + + // description of the tcp.Target + // @inject_tag: `gorm:"default:null"` + string description = 40 [(custom_options.v1.mask_mapping) = { + this: "description" + that: "description" + }]; + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 50; + + // update_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 60; + + // version allows optimistic locking of the tcp.Target when modifying the + // tcp.Target + // @inject_tag: `gorm:"default:null"` + uint32 version = 70; + + // default port of the tcp.Target + // @inject_tag: `gorm:"default:null"` + uint32 default_port = 80 [(custom_options.v1.mask_mapping) = { + this: "DefaultPort" + that: "attributes.default_port" + }]; + + // Maximum total lifetime of a created session, in seconds + // @inject_tag: `gorm:"default:null"` + uint32 session_max_seconds = 100 [(custom_options.v1.mask_mapping) = { + this: "SessionMaxSeconds" + that: "session_max_seconds" + }]; + + // Maximum number of connections in a session + // @inject_tag: `gorm:"default:null"` + int32 session_connection_limit = 110 [(custom_options.v1.mask_mapping) = { + this: "SessionConnectionLimit" + that: "session_connection_limit" + }]; + + // A boolean expression that allows filtering the workers that can handle a session + // @inject_tag: `gorm:"default:null"` + string worker_filter = 120 [(custom_options.v1.mask_mapping) = { + this: "WorkerFilter" + that: "worker_filter" + }]; +} + diff --git a/internal/proto/local/errors/v1/errors.proto b/internal/proto/local/errors/v1/errors.proto new file mode 100644 index 000000000..d8acb33fc --- /dev/null +++ b/internal/proto/local/errors/v1/errors.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package errors.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/errors;errors"; + +import "controller/api/v1/error.proto"; + +// Err is a protobuf representation of a domain error +message Err { + // code is the error's code, which can be used to get the error's + // errorCodeInfo, which contains the error's Kind and Message + uint32 code = 10; + + // msg for the error + string msg = 20; + + // op represents the operation raising/propagating an error and is optional. + string op = 30; + + // wrapped represents an optionally wrapped error + oneof wrapped { + // err is a wrapped Err (aka a domain error) + Err err = 40; + + // std_error is a wrapped std error + string std_error = 50; + + // no error or err is wrapped + bool none = 60; + } +} + +// ApiError is a protobuf representation of a controller ApiError +message ApiError { + // api_error is the controller ApiError + controller.api.v1.Error api_error = 10; + + // status is the status code assocatiated with the api error + int32 status = 20; +} \ No newline at end of file diff --git a/internal/proto/local/testing/interceptor/v1/greeter.proto b/internal/proto/local/testing/interceptor/v1/greeter.proto new file mode 100644 index 000000000..f1fc1b4e5 --- /dev/null +++ b/internal/proto/local/testing/interceptor/v1/greeter.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package testing.interceptor.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/testing/interceptor;interceptor"; + +// The greeting service definition. This is just a dead simple service that can +// be used to test interceptors. (See controller.startTestGreeterService) +service GreeterService { + // Sends a greeting + rpc SayHello(SayHelloRequest) returns (SayHelloResponse) {} +} + +// The request message containing the user's name. +message SayHelloRequest { + string name = 1; +} + +// The response message containing the greetings +message SayHelloResponse { + string message = 1; +} \ No newline at end of file diff --git a/internal/servers/controller/auth/auth.go b/internal/servers/controller/auth/auth.go index 6fd9f6946..b72d4b7c5 100644 --- a/internal/servers/controller/auth/auth.go +++ b/internal/servers/controller/auth/auth.go @@ -11,7 +11,9 @@ import ( "github.com/hashicorp/boundary/api/recovery" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/errors" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/gen/controller/tokens" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/boundary/internal/perms" @@ -27,7 +29,7 @@ import ( "google.golang.org/protobuf/proto" ) -type TokenFormat int +type TokenFormat uint32 const ( // We weren't given one or couldn't parse it @@ -51,22 +53,6 @@ type key int var verifierKey key -// RequestInfo contains request parameters necessary for checking authn/authz -type RequestInfo struct { - Path string - Method string - PublicId string - EncryptedToken string - Token string - TokenFormat TokenFormat - - // The following are useful for tests - scopeIdOverride string - userIdOverride string - DisableAuthzFailures bool - DisableAuthEntirely bool -} - type VerifyResults struct { UserId string AuthTokenId string @@ -108,7 +94,7 @@ type verifier struct { authTokenRepoFn common.AuthTokenRepoFactory serversRepoFn common.ServersRepoFactory kms *kms.Kms - requestInfo RequestInfo + requestInfo *authpb.RequestInfo res *perms.Resource act action.Type ctx context.Context @@ -124,7 +110,7 @@ func NewVerifierContext(ctx context.Context, authTokenRepoFn common.AuthTokenRepoFactory, serversRepoFn common.ServersRepoFactory, kms *kms.Kms, - requestInfo RequestInfo) context.Context { + requestInfo *authpb.RequestInfo) context.Context { return context.WithValue(ctx, verifierKey, &verifier{ iamRepoFn: iamRepoFn, authTokenRepoFn: authTokenRepoFn, @@ -174,7 +160,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) { // without fuss if v.requestInfo.DisableAuthEntirely { const op = "auth.(disabled).lookupScope" - ret.Scope.Id = v.requestInfo.scopeIdOverride + ret.Scope.Id = v.requestInfo.ScopeIdOverride if ret.Scope.Id == "" { ret.Scope.Id = opts.withScopeId } @@ -214,7 +200,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) { ParentScopeId: scp.GetParentId(), } } - ret.UserId = v.requestInfo.userIdOverride + ret.UserId = v.requestInfo.UserIdOverride if reqInfo != nil { reqInfo.UserId = ret.UserId } @@ -291,40 +277,40 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) { func (v *verifier) decryptToken(ctx context.Context) { const op = "auth.(verifier).decryptToken" switch v.requestInfo.TokenFormat { - case AuthTokenTypeUnknown: + case uint32(AuthTokenTypeUnknown): // Nothing to decrypt return - case AuthTokenTypeBearer, AuthTokenTypeSplitCookie: + case uint32(AuthTokenTypeBearer), uint32(AuthTokenTypeSplitCookie): if v.kms == nil { event.WriteError(ctx, op, stderrors.New("no KMS object available to authz system")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } tokenRepo, err := v.authTokenRepoFn() if err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("failed to get authtoken repo")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } at, err := tokenRepo.LookupAuthToken(v.ctx, v.requestInfo.PublicId) if err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("failed to look up auth token by public ID")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } if at == nil { event.WriteError(ctx, op, stderrors.New("nil result from looking up auth token by public ID")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } tokenWrapper, err := v.kms.GetWrapper(v.ctx, at.GetScopeId(), kms.KeyPurposeTokens) if err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("unable to get wrapper for tokens; continuing as anonymous user")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } @@ -333,62 +319,62 @@ func (v *verifier) decryptToken(ctx context.Context) { case globals.ServiceTokenV1: default: event.WriteError(ctx, op, stderrors.New("unknown token encryption version; continuing as anonymous user"), event.WithInfo("version", version)) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } marshaledToken, err := base58.FastBase58Decoding(v.requestInfo.EncryptedToken[len(globals.ServiceTokenV1):]) if err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("error unmarshaling base58 token; continuing as anonymous user")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } blobInfo := new(wrapping.EncryptedBlobInfo) if err := proto.Unmarshal(marshaledToken, blobInfo); err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("error decoding encrypted token; continuing as anonymous user")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } s1Bytes, err := tokenWrapper.Decrypt(v.ctx, blobInfo, []byte(v.requestInfo.PublicId)) if err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("error decrypting encrypted token; continuing as anonymous user")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } var s1Info tokens.S1TokenInfo if err := proto.Unmarshal(s1Bytes, &s1Info); err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("error unmarshaling token info; continuing as anonymous user")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } - if v.requestInfo.TokenFormat == AuthTokenTypeUnknown || s1Info.Token == "" || v.requestInfo.PublicId == "" { + if v.requestInfo.TokenFormat == uint32(AuthTokenTypeUnknown) || s1Info.Token == "" || v.requestInfo.PublicId == "" { event.WriteError(ctx, op, stderrors.New("after parsing, could not find valid token; continuing as anonymous user")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } v.requestInfo.Token = s1Info.Token return - case AuthTokenTypeRecoveryKms: + case uint32(AuthTokenTypeRecoveryKms): if v.kms == nil { event.WriteError(ctx, op, stderrors.New("no KMS object available to authz system")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } wrapper := v.kms.GetExternalWrappers().Recovery() if wrapper == nil { event.WriteError(ctx, op, stderrors.New("no recovery KMS is available")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } info, err := recovery.ParseRecoveryToken(v.ctx, wrapper, v.requestInfo.EncryptedToken) if err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("decrypt recovery token: error parsing and validating recovery token")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } // If we add the validity period to the creation time (which we've @@ -396,18 +382,18 @@ func (v *verifier) decryptToken(ctx context.Context) { // it's before now, it's expired and might be a replay. if info.CreationTime.Add(globals.RecoveryTokenValidityPeriod).Before(time.Now()) { event.WriteError(ctx, op, stderrors.New("recovery token has expired (possible replay attack)")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } repo, err := v.serversRepoFn() if err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("decrypt recovery token: error fetching servers repo")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("decrypt recovery token: error adding nonce to database (possible replay attack)")) - v.requestInfo.TokenFormat = AuthTokenTypeUnknown + v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown) return } event.WriteError(ctx, op, stderrors.New("recovery KMS was used to authorize a call"), event.WithInfo("url", v.requestInfo.Path, "method", v.requestInfo.Method)) @@ -426,15 +412,15 @@ func (v verifier) performAuthCheck(ctx context.Context) (aclResults perms.ACLRes // Validate the token and fetch the corresponding user ID switch v.requestInfo.TokenFormat { - case AuthTokenTypeUnknown: + case uint32(AuthTokenTypeUnknown): // Nothing; remain as the anonymous user - case AuthTokenTypeRecoveryKms: + case uint32(AuthTokenTypeRecoveryKms): // We validated the encrypted token in decryptToken and handled the // nonces there, so just set the user userId = "u_recovery" - case AuthTokenTypeBearer, AuthTokenTypeSplitCookie: + case uint32(AuthTokenTypeBearer), uint32(AuthTokenTypeSplitCookie): if v.requestInfo.Token == "" { // This will end up staying as the anonymous user break @@ -500,7 +486,7 @@ func (v verifier) performAuthCheck(ctx context.Context) (aclResults perms.ACLRes } // At this point we don't need to look up grants since it's automatically allowed - if v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms { + if v.requestInfo.TokenFormat == uint32(AuthTokenTypeRecoveryKms) { aclResults.AuthenticationFinished = true aclResults.Authorized = true retErr = nil @@ -561,7 +547,7 @@ func (r *VerifyResults) FetchActionSetForType(ctx context.Context, typ resource. func (r *VerifyResults) fetchActions(id string, typ resource.Type, availableActions action.ActionSet, opt ...Option) action.ActionSet { switch { case r.v.requestInfo.DisableAuthEntirely, - r.v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms: + r.v.requestInfo.TokenFormat == uint32(AuthTokenTypeRecoveryKms): // TODO: See if we can be better about what we return with the anonymous // user and recovery KMS, perhaps given exclusionary options for each return availableActions @@ -598,7 +584,7 @@ func (r *VerifyResults) fetchActions(id string, typ resource.Type, availableActi func (r *VerifyResults) FetchOutputFields(res perms.Resource, act action.Type) perms.OutputFieldsMap { switch { - case r.v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms: + case r.v.requestInfo.TokenFormat == uint32(AuthTokenTypeRecoveryKms): return perms.OutputFieldsMap{"*": true} case r.v.requestInfo.DisableAuthEntirely: return nil @@ -611,7 +597,7 @@ func (r *VerifyResults) FetchOutputFields(res perms.Resource, act action.Type) p // split cookies and parses it. If it cannot be parsed successfully, the issue // is logged and we return blank, so logic will continue as the anonymous user. // The public ID and _encrypted_ token are returned along with the token format. -func GetTokenFromRequest(ctx context.Context, kmsCache *kms.Kms, req *http.Request) (string, string, TokenFormat) { +func GetTokenFromRequest(ctx context.Context, kmsCache *kms.Kms, req *http.Request) (string, string, uint32) { const op = "auth.GetTokenFromRequest" // First, get the token, either from the authorization header or from split // cookies @@ -643,21 +629,21 @@ func GetTokenFromRequest(ctx context.Context, kmsCache *kms.Kms, req *http.Reque // We didn't find auth info or a client screwed up and put in a blank // header instead of nothing at all, so return blank which will indicate // the anonymouse user - return "", "", AuthTokenTypeUnknown + return "", "", uint32(AuthTokenTypeUnknown) } if strings.HasPrefix(fullToken, "r_") { - return "", fullToken, AuthTokenTypeRecoveryKms + return "", fullToken, uint32(AuthTokenTypeRecoveryKms) } splitFullToken := strings.Split(fullToken, "_") if len(splitFullToken) != 3 { event.WriteError(ctx, op, stderrors.New("unexpected number of segments in token; continuing as anonymous user"), event.WithInfo("expected", 3, "found", len(splitFullToken))) - return "", "", AuthTokenTypeUnknown + return "", "", uint32(AuthTokenTypeUnknown) } publicId := strings.Join(splitFullToken[0:2], "_") encryptedToken := splitFullToken[2] - return publicId, encryptedToken, receivedTokenType + return publicId, encryptedToken, uint32(receivedTokenType) } diff --git a/internal/servers/controller/auth/auth_test.go b/internal/servers/controller/auth/auth_test.go index 574ac0cb3..e90b523f2 100644 --- a/internal/servers/controller/auth/auth_test.go +++ b/internal/servers/controller/auth/auth_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/db" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers" @@ -107,17 +108,17 @@ func TestAuthTokenAuthenticator(t *testing.T) { } // Add values for authn/authz checking - requestInfo := RequestInfo{ + requestInfo := authpb.RequestInfo{ Path: req.URL.Path, Method: req.Method, } requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(context.TODO(), kms, req) - assert.Equal(t, tc.tokenFormat, requestInfo.TokenFormat) + assert.Equal(t, uint32(tc.tokenFormat), requestInfo.TokenFormat) if tc.userId == "" { return } - ctx := NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo) + ctx := NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) v, ok := ctx.Value(verifierKey).(*verifier) require.True(t, ok) diff --git a/internal/servers/controller/auth/testing.go b/internal/servers/controller/auth/testing.go index 731da8bd2..6a50b5b49 100644 --- a/internal/servers/controller/auth/testing.go +++ b/internal/servers/controller/auth/testing.go @@ -3,6 +3,7 @@ package auth import ( "context" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/servers/controller/common" ) @@ -11,16 +12,16 @@ import ( // auth checking entirely disabled. Supported options: WithScopeId an WithUserId // are used directly; WithKms is passed through into the verifier context. func DisabledAuthTestContext(iamRepoFn common.IamRepoFactory, scopeId string, opt ...Option) context.Context { - reqInfo := RequestInfo{DisableAuthEntirely: true} + reqInfo := authpb.RequestInfo{DisableAuthEntirely: true} opts := getOpts(opt...) - reqInfo.scopeIdOverride = opts.withScopeId - if reqInfo.scopeIdOverride == "" { - reqInfo.scopeIdOverride = scopeId + reqInfo.ScopeIdOverride = opts.withScopeId + if reqInfo.ScopeIdOverride == "" { + reqInfo.ScopeIdOverride = scopeId } - reqInfo.userIdOverride = opts.withUserId - if reqInfo.userIdOverride == "" { - reqInfo.userIdOverride = "u_auth" + reqInfo.UserIdOverride = opts.withUserId + if reqInfo.UserIdOverride == "" { + reqInfo.UserIdOverride = "u_auth" } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) - return NewVerifierContext(requestContext, iamRepoFn, nil, nil, opts.withKms, reqInfo) + return NewVerifierContext(requestContext, iamRepoFn, nil, nil, opts.withKms, &reqInfo) } diff --git a/internal/servers/controller/common/scopeids/scope_ids_test.go b/internal/servers/controller/common/scopeids/scope_ids_test.go index 570098e9d..d0598b975 100644 --- a/internal/servers/controller/common/scopeids/scope_ids_test.go +++ b/internal/servers/controller/common/scopeids/scope_ids_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers" @@ -229,9 +230,9 @@ func TestListingScopeIds(t *testing.T) { authTokenRepoFn, serversRepoFn, kms, - auth.RequestInfo{ + &authpb.RequestInfo{ Token: at.GetToken(), - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), }) diff --git a/internal/servers/controller/controller.go b/internal/servers/controller/controller.go index 9aa83c540..7e6175878 100644 --- a/internal/servers/controller/controller.go +++ b/internal/servers/controller/controller.go @@ -6,6 +6,8 @@ import ( "fmt" "sync" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" @@ -32,6 +34,7 @@ import ( "github.com/hashicorp/go-secure-stdlib/mlock" "github.com/patrickmn/go-cache" ua "go.uber.org/atomic" + "google.golang.org/grpc" ) type Controller struct { @@ -50,6 +53,12 @@ type Controller struct { // Used for testing and tracking worker health workerStatusUpdateTimes *sync.Map + // grpc gateway server + gatewayServer *grpc.Server + gatewayTicket string + gatewayListener gatewayListener + gatewayMux *runtime.ServeMux + // Repo factory methods AuthTokenRepoFn common.AuthTokenRepoFactory VaultCredentialRepoFn common.VaultCredentialRepoFactory @@ -218,7 +227,7 @@ func (c *Controller) Start() error { if err := c.scheduler.Start(c.baseContext, &c.schedulerWg); err != nil { return fmt.Errorf("error starting scheduler: %w", err) } - if err := c.startListeners(); err != nil { + if err := c.startListeners(c.baseContext); err != nil { return fmt.Errorf("error starting controller listeners: %w", err) } diff --git a/internal/servers/controller/gateway.go b/internal/servers/controller/gateway.go new file mode 100644 index 000000000..41f6d76a9 --- /dev/null +++ b/internal/servers/controller/gateway.go @@ -0,0 +1,67 @@ +package controller + +import ( + "context" + "math" + "net" + + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "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/servers/controller/common" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "google.golang.org/grpc" + "google.golang.org/grpc/test/bufconn" +) + +// newGatewayListener will create an in-memory listener +func newGatewayListener() (gatewayListener, string) { + buffer := globals.DefaultMaxRequestSize // seems like a reasonable size for the ring buffer, but then happily change the size if more info becomes available + return bufconn.Listen(int(buffer)), "" +} + +const gatewayTarget = "" + +type gatewayListener interface { + net.Listener + Dial() (net.Conn, error) +} + +func gatewayDialOptions(lis gatewayListener) []grpc.DialOption { + return []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return lis.Dial() + }), + } +} + +func newGatewayMux() *runtime.ServeMux { + return runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{ + Marshaler: handlers.JSONMarshaler(), + }), + runtime.WithErrorHandler(handlers.ErrorHandler()), + runtime.WithForwardResponseOption(handlers.OutgoingResponseFilter), + ) +} + +func newGatewayServer(ctx context.Context, iamRepoFn common.IamRepoFactory, authTokenRepoFn common.AuthTokenRepoFactory, serversRepoFn common.ServersRepoFactory, kms *kms.Kms) (*grpc.Server, string, error) { + const op = "controller.newGatewayServer" + ticket, err := db.NewPrivateId("gwticket") + if err != nil { + return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg("unable to generate gateway ticket")) + } + requestCtxInterceptor, err := requestCtxInterceptor(ctx, iamRepoFn, authTokenRepoFn, serversRepoFn, kms, ticket) + if err != nil { + return nil, "", err + } + return grpc.NewServer( + grpc.MaxRecvMsgSize(math.MaxInt32), + grpc.MaxSendMsgSize(math.MaxInt32), + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(requestCtxInterceptor, errorInterceptor(ctx), statusCodeInterceptor(ctx))), + ), ticket, nil +} diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index d59a39c82..43b9b72a2 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -13,35 +13,36 @@ import ( "strings" "time" - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/gen/controller/api/services" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/observability/event" - "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/servers/common" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/servers/controller/handlers/accounts" "github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens" "github.com/hashicorp/boundary/internal/servers/controller/handlers/credentiallibraries" "github.com/hashicorp/boundary/internal/servers/controller/handlers/credentialstores" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/groups" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_catalogs" "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_sets" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/hosts" "github.com/hashicorp/boundary/internal/servers/controller/handlers/managed_groups" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/roles" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/scopes" "github.com/hashicorp/boundary/internal/servers/controller/handlers/sessions" "github.com/hashicorp/boundary/internal/servers/controller/handlers/targets" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/users" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-secure-stdlib/listenerutil" "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mr-tron/base58" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" "github.com/hashicorp/boundary/internal/servers/controller/handlers" - "github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens" - "github.com/hashicorp/boundary/internal/servers/controller/handlers/groups" - "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_catalogs" - "github.com/hashicorp/boundary/internal/servers/controller/handlers/hosts" - "github.com/hashicorp/boundary/internal/servers/controller/handlers/roles" - "github.com/hashicorp/boundary/internal/servers/controller/handlers/scopes" - "github.com/hashicorp/boundary/internal/servers/controller/handlers/users" ) type HandlerProperties struct { @@ -78,129 +79,168 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er // Register*ServiceHandlerServer methods ignore the passed in ctx. Using it // now however in case this changes in the future. ctx := props.CancelCtx - mux := runtime.NewServeMux( - runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{ - Marshaler: handlers.JSONMarshaler(), - }), - runtime.WithErrorHandler(handlers.ErrorHandler()), - runtime.WithForwardResponseOption(handlers.OutgoingInterceptor), - ) - hcs, err := host_catalogs.NewService(c.StaticHostRepoFn, c.PluginHostRepoFn, c.HostPluginRepoFn, c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create host catalog handler service: %w", err) + currentServices := c.gatewayServer.GetServiceInfo() + dialOptions := gatewayDialOptions(c.gatewayListener) + + if _, ok := currentServices[services.HostCatalogService_ServiceDesc.ServiceName]; !ok { + hcs, err := host_catalogs.NewService(c.StaticHostRepoFn, c.PluginHostRepoFn, c.HostPluginRepoFn, c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create host catalog handler service: %w", err) + } + services.RegisterHostCatalogServiceServer(c.gatewayServer, hcs) + if err := services.RegisterHostCatalogServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register host catalog service handler: %w", err) + } } - if err := services.RegisterHostCatalogServiceHandlerServer(ctx, mux, hcs); err != nil { - return nil, fmt.Errorf("failed to register host catalog service handler: %w", err) + if _, ok := currentServices[services.HostSetService_ServiceDesc.ServiceName]; !ok { + hss, err := host_sets.NewService(c.StaticHostRepoFn, c.PluginHostRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create host set handler service: %w", err) + } + services.RegisterHostSetServiceServer(c.gatewayServer, hss) + if err := services.RegisterHostSetServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register host set service handler: %w", err) + } } - hss, err := host_sets.NewService(c.StaticHostRepoFn, c.PluginHostRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create host set handler service: %w", err) + if _, ok := currentServices[services.HostService_ServiceDesc.ServiceName]; !ok { + hs, err := hosts.NewService(c.StaticHostRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create host handler service: %w", err) + } + services.RegisterHostServiceServer(c.gatewayServer, hs) + if err := services.RegisterHostServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register host service handler: %w", err) + } } - if err := services.RegisterHostSetServiceHandlerServer(ctx, mux, hss); err != nil { - return nil, fmt.Errorf("failed to register host set service handler: %w", err) + if _, ok := currentServices[services.AccountService_ServiceDesc.ServiceName]; !ok { + accts, err := accounts.NewService(c.PasswordAuthRepoFn, c.OidcRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create account handler service: %w", err) + } + services.RegisterAccountServiceServer(c.gatewayServer, accts) + if err := services.RegisterAccountServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register account service handler: %w", err) + } } - hs, err := hosts.NewService(c.StaticHostRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create host handler service: %w", err) + if _, ok := currentServices[services.AuthMethodService_ServiceDesc.ServiceName]; !ok { + authMethods, err := authmethods.NewService(c.kms, c.PasswordAuthRepoFn, c.OidcRepoFn, c.IamRepoFn, c.AuthTokenRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create auth method handler service: %w", err) + } + services.RegisterAuthMethodServiceServer(c.gatewayServer, authMethods) + if err := services.RegisterAuthMethodServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register auth method service handler: %w", err) + } } - if err := services.RegisterHostServiceHandlerServer(ctx, mux, hs); err != nil { - return nil, fmt.Errorf("failed to register host service handler: %w", err) + if _, ok := currentServices[services.AuthTokenService_ServiceDesc.ServiceName]; !ok { + authtoks, err := authtokens.NewService(c.AuthTokenRepoFn, c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create auth token handler service: %w", err) + } + services.RegisterAuthTokenServiceServer(c.gatewayServer, authtoks) + if err := services.RegisterAuthTokenServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register auth token service handler: %w", err) + } } - accts, err := accounts.NewService(c.PasswordAuthRepoFn, c.OidcRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create account handler service: %w", err) + if _, ok := currentServices[services.ScopeService_ServiceDesc.ServiceName]; !ok { + os, err := scopes.NewService(c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create scope handler service: %w", err) + } + services.RegisterScopeServiceServer(c.gatewayServer, os) + if err := services.RegisterScopeServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register scope service handler: %w", err) + } } - if err := services.RegisterAccountServiceHandlerServer(ctx, mux, accts); err != nil { - return nil, fmt.Errorf("failed to register account service handler: %w", err) + if _, ok := currentServices[services.UserService_ServiceDesc.ServiceName]; !ok { + us, err := users.NewService(c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create user handler service: %w", err) + } + services.RegisterUserServiceServer(c.gatewayServer, us) + if err := services.RegisterUserServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register user service handler: %w", err) + } } - authMethods, err := authmethods.NewService(c.kms, c.PasswordAuthRepoFn, c.OidcRepoFn, c.IamRepoFn, c.AuthTokenRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create auth method handler service: %w", err) + if _, ok := currentServices[services.TargetService_ServiceDesc.ServiceName]; !ok { + ts, err := targets.NewService( + c.kms, + c.TargetRepoFn, + c.IamRepoFn, + c.ServersRepoFn, + c.SessionRepoFn, + c.StaticHostRepoFn, + c.VaultCredentialRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create target handler service: %w", err) + } + services.RegisterTargetServiceServer(c.gatewayServer, ts) + if err := services.RegisterTargetServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register target service handler: %w", err) + } } - if err := services.RegisterAuthMethodServiceHandlerServer(ctx, mux, authMethods); err != nil { - return nil, fmt.Errorf("failed to register auth method service handler: %w", err) + if _, ok := currentServices[services.GroupService_ServiceDesc.ServiceName]; !ok { + gs, err := groups.NewService(c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create group handler service: %w", err) + } + services.RegisterGroupServiceServer(c.gatewayServer, gs) + if err := services.RegisterGroupServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register group service handler: %w", err) + } } - authtoks, err := authtokens.NewService(c.AuthTokenRepoFn, c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create auth token handler service: %w", err) + if _, ok := currentServices[services.RoleService_ServiceDesc.ServiceName]; !ok { + rs, err := roles.NewService(c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create role handler service: %w", err) + } + services.RegisterRoleServiceServer(c.gatewayServer, rs) + if err := services.RegisterRoleServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register role service handler: %w", err) + } } - if err := services.RegisterAuthTokenServiceHandlerServer(ctx, mux, authtoks); err != nil { - return nil, fmt.Errorf("failed to register auth token service handler: %w", err) + if _, ok := currentServices[services.SessionService_ServiceDesc.ServiceName]; !ok { + ss, err := sessions.NewService(c.SessionRepoFn, c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create session handler service: %w", err) + } + services.RegisterSessionServiceServer(c.gatewayServer, ss) + if err := services.RegisterSessionServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register session service handler: %w", err) + } } - os, err := scopes.NewService(c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create scope handler service: %w", err) + if _, ok := currentServices[services.ManagedGroupService_ServiceDesc.ServiceName]; !ok { + mgs, err := managed_groups.NewService(c.OidcRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create managed groups handler service: %w", err) + } + services.RegisterManagedGroupServiceServer(c.gatewayServer, mgs) + if err := services.RegisterManagedGroupServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register managed groups service handler: %w", err) + } } - if err := services.RegisterScopeServiceHandlerServer(ctx, mux, os); err != nil { - return nil, fmt.Errorf("failed to register scope service handler: %w", err) + if _, ok := currentServices[services.CredentialStoreService_ServiceDesc.ServiceName]; !ok { + cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create credential store handler service: %w", err) + } + services.RegisterCredentialStoreServiceServer(c.gatewayServer, cs) + if err := services.RegisterCredentialStoreServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register credential store service handler: %w", err) + } } - us, err := users.NewService(c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create user handler service: %w", err) - } - if err := services.RegisterUserServiceHandlerServer(ctx, mux, us); err != nil { - return nil, fmt.Errorf("failed to register user service handler: %w", err) - } - ts, err := targets.NewService( - ctx, - c.kms, - c.TargetRepoFn, - c.IamRepoFn, - c.ServersRepoFn, - c.SessionRepoFn, - c.PluginHostRepoFn, - c.StaticHostRepoFn, - c.VaultCredentialRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create target handler service: %w", err) - } - if err := services.RegisterTargetServiceHandlerServer(ctx, mux, ts); err != nil { - return nil, fmt.Errorf("failed to register target service handler: %w", err) - } - gs, err := groups.NewService(c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create group handler service: %w", err) - } - if err := services.RegisterGroupServiceHandlerServer(ctx, mux, gs); err != nil { - return nil, fmt.Errorf("failed to register group service handler: %w", err) - } - rs, err := roles.NewService(c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create role handler service: %w", err) - } - if err := services.RegisterRoleServiceHandlerServer(ctx, mux, rs); err != nil { - return nil, fmt.Errorf("failed to register role service handler: %w", err) - } - ss, err := sessions.NewService(c.SessionRepoFn, c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create session handler service: %w", err) - } - if err := services.RegisterSessionServiceHandlerServer(ctx, mux, ss); err != nil { - return nil, fmt.Errorf("failed to register session service handler: %w", err) - } - mgs, err := managed_groups.NewService(c.OidcRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create managed groups handler service: %w", err) - } - if err := services.RegisterManagedGroupServiceHandlerServer(ctx, mux, mgs); err != nil { - return nil, fmt.Errorf("failed to register managed groups service handler: %w", err) - } - cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create credential store handler service: %w", err) - } - if err := services.RegisterCredentialStoreServiceHandlerServer(ctx, mux, cs); err != nil { - return nil, fmt.Errorf("failed to register credential store service handler: %w", err) - } - cl, err := credentiallibraries.NewService(c.VaultCredentialRepoFn, c.IamRepoFn) - if err != nil { - return nil, fmt.Errorf("failed to create credential library handler service: %w", err) - } - if err := services.RegisterCredentialLibraryServiceHandlerServer(ctx, mux, cl); err != nil { - return nil, fmt.Errorf("failed to register credential library service handler: %w", err) + if _, ok := currentServices[services.CredentialLibraryService_ServiceDesc.ServiceName]; !ok { + cl, err := credentiallibraries.NewService(c.VaultCredentialRepoFn, c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create credential library handler service: %w", err) + } + services.RegisterCredentialLibraryServiceServer(c.gatewayServer, cl) + if err := services.RegisterCredentialLibraryServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil { + return nil, fmt.Errorf("failed to register credential library service handler: %w", err) + } } - return mux, nil + return c.gatewayMux, nil } func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProperties) http.Handler { @@ -238,29 +278,27 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp } // Add values for authn/authz checking - requestInfo := auth.RequestInfo{ + requestInfo := authpb.RequestInfo{ Path: r.URL.Path, Method: r.Method, DisableAuthzFailures: disableAuthzFailures, } requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(ctx, c.kms, r) - ctx = auth.NewVerifierContext(ctx, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms, requestInfo) - // Add general request information to the context. The information from - // the auth verifier context is pretty specifically curated to - // authentication/authorization verification so this is more - // general-purpose. - // - // We could use requests.NewRequestContext but this saves an immediate - // lookup. - ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{ - Path: r.URL.Path, - Method: r.Method, - }) - - // Set the context back on the request - r = r.WithContext(ctx) + // Serialize the request info to send it across the wire to the + // grpc-gateway via an http header + requestInfo.Ticket = c.gatewayTicket // allows the grpc-gateway to verify the request info came from it's in-memory companion http proxy + marshalledRequestInfo, err := proto.Marshal(&requestInfo) + if err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("error marshaling request info")) + w.WriteHeader(http.StatusInternalServerError) + return + } + // Use the default grpc-gateway mapping rule to pass the request info as + // metadata. + // See: https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime#DefaultHeaderMatcher + r.Header.Set("Grpc-Metadata-"+requestInfoMdKey, base58.FastBase58Encoding(marshalledRequestInfo)) h.ServeHTTP(w, r) }) diff --git a/internal/servers/controller/handlers/authmethods/authmethod_service.go b/internal/servers/controller/handlers/authmethods/authmethod_service.go index 4fb5f0aa2..4bf43902a 100644 --- a/internal/servers/controller/handlers/authmethods/authmethod_service.go +++ b/internal/servers/controller/handlers/authmethods/authmethod_service.go @@ -389,7 +389,7 @@ func (s Service) DeleteAuthMethod(ctx context.Context, req *pbs.DeleteAuthMethod if err != nil { return nil, err } - return nil, nil + return &pbs.DeleteAuthMethodResponse{}, nil } // Authenticate implements the interface pbs.AuthenticationServiceServer. diff --git a/internal/servers/controller/handlers/authmethods/authmethod_service_test.go b/internal/servers/controller/handlers/authmethods/authmethod_service_test.go index f1d472a11..8dbd20d93 100644 --- a/internal/servers/controller/handlers/authmethods/authmethod_service_test.go +++ b/internal/servers/controller/handlers/authmethods/authmethod_service_test.go @@ -428,12 +428,14 @@ func TestDelete(t *testing.T) { req: &pbs.DeleteAuthMethodRequest{ Id: pwam.GetPublicId(), }, + res: &pbs.DeleteAuthMethodResponse{}, }, { name: "Delete an Existing OIDC AuthMethod", req: &pbs.DeleteAuthMethodRequest{ Id: oidcam.GetPublicId(), }, + res: &pbs.DeleteAuthMethodResponse{}, }, { name: "Delete bad auth_method id", diff --git a/internal/servers/controller/handlers/authmethods/oidc_test.go b/internal/servers/controller/handlers/authmethods/oidc_test.go index d843c4603..ddc308af8 100644 --- a/internal/servers/controller/handlers/authmethods/oidc_test.go +++ b/internal/servers/controller/handlers/authmethods/oidc_test.go @@ -13,6 +13,8 @@ import ( "time" "github.com/google/go-cmp/cmp" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" + "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" @@ -206,9 +208,9 @@ func TestList_FilterNonPublic(t *testing.T) { atRepoFn, serversRepoFn, kmsCache, - auth.RequestInfo{ + &authpb.RequestInfo{ Token: at.GetToken(), - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), }) }(), diff --git a/internal/servers/controller/handlers/authtokens/authtoken_service_test.go b/internal/servers/controller/handlers/authtokens/authtoken_service_test.go index 784ac8a3e..cff486682 100644 --- a/internal/servers/controller/handlers/authtokens/authtoken_service_test.go +++ b/internal/servers/controller/handlers/authtokens/authtoken_service_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/requests" @@ -87,15 +88,15 @@ func TestGetSelf(t *testing.T) { require, assert := require.New(t), assert.New(t) // Setup the auth request information req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens/%s", tc.readId), nil) - requestInfo := auth.RequestInfo{ + requestInfo := authpb.RequestInfo{ Path: req.URL.Path, Method: req.Method, - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: tc.token.GetPublicId(), Token: tc.token.GetToken(), } - ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo) + ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{}) got, err := a.GetAuthToken(ctx, &pbs.GetAuthTokenRequest{Id: tc.readId}) if tc.err != nil { @@ -236,15 +237,15 @@ func TestList_Self(t *testing.T) { require, assert := require.New(t), assert.New(t) // Setup the auth request information req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens?scope_id=%s", o.GetPublicId()), nil) - requestInfo := auth.RequestInfo{ + requestInfo := authpb.RequestInfo{ Path: req.URL.Path, Method: req.Method, - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: tc.requester.GetPublicId(), Token: tc.requester.GetToken(), } - ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo) + ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := a.ListAuthTokens(ctx, &pbs.ListAuthTokensRequest{ScopeId: o.GetPublicId()}) require.NoError(err) require.Len(got.Items, 1) @@ -482,15 +483,15 @@ func TestDeleteSelf(t *testing.T) { require := require.New(t) // Setup the auth request information req := httptest.NewRequest("DELETE", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens/%s", tc.deleteId), nil) - requestInfo := auth.RequestInfo{ + requestInfo := authpb.RequestInfo{ Path: req.URL.Path, Method: req.Method, - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: tc.token.GetPublicId(), Token: tc.token.GetToken(), } - ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo) + ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := a.DeleteAuthToken(ctx, &pbs.DeleteAuthTokenRequest{Id: tc.deleteId}) if tc.err != nil { require.EqualError(err, tc.err.Error()) diff --git a/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service_test.go b/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service_test.go index d9dd0118b..681294a1a 100644 --- a/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service_test.go +++ b/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service_test.go @@ -555,6 +555,7 @@ func TestDelete(t *testing.T) { name string id string err error + res *pbs.DeleteCredentialLibraryResponse }{ { name: "success", @@ -574,7 +575,7 @@ func TestDelete(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got, gErr := s.DeleteCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialLibraryRequest{Id: tc.id}) - assert.Nil(t, got) + assert.EqualValuesf(t, tc.res, got, "DeleteCredentialLibrary got response %q, wanted %q", got, tc.res) if tc.err != nil { require.Error(t, gErr) assert.True(t, errors.Is(gErr, tc.err)) diff --git a/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go b/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go index 9e087bd46..bfa43c54b 100644 --- a/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go +++ b/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go @@ -674,6 +674,7 @@ func TestDelete(t *testing.T) { name string id string err error + res *pbs.DeleteCredentialStoreResponse }{ { name: "success", @@ -693,7 +694,7 @@ func TestDelete(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got, gErr := s.DeleteCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialStoreRequest{Id: tc.id}) - assert.Nil(t, got) + assert.EqualValuesf(t, tc.res, got, "DeleteCredentialStore got response %q, wanted %q", got, tc.res) if tc.err != nil { require.Error(t, gErr) assert.True(t, errors.Is(gErr, tc.err)) diff --git a/internal/servers/controller/handlers/errors.go b/internal/servers/controller/handlers/errors.go index 1628de364..c23e59164 100644 --- a/internal/servers/controller/handlers/errors.go +++ b/internal/servers/controller/handlers/errors.go @@ -5,31 +5,46 @@ import ( "fmt" "io" "net/http" + "regexp" "sort" + "strconv" "strings" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/hashicorp/boundary/internal/errors" pb "github.com/hashicorp/boundary/internal/gen/controller/api" + pberrors "github.com/hashicorp/boundary/internal/gen/errors" "github.com/hashicorp/boundary/internal/observability/event" + "github.com/mr-tron/base58" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" ) const ( genericUniquenessMsg = "Invalid request. Request attempted to make second resource with the same field value that must be unique." genericNotFoundMsg = "Unable to find requested resource." + + // domainErrHeader defines an http header for encoded domain errors from the + // grpc server. + domainErrHeader = "x-domain-err" + // domainErrMetadataHeader defines an http header for encoded domain errors from the + // grpc server via metadata + domainErrMetadataHeader = "Grpc-Metadata-X-Domain-Err" + + apiErrHeader = "x-api-err" + apiErrMetadataHeader = "Grpc-Metadata-X-Api-Err" ) -type apiError struct { - status int32 - inner *pb.Error +type ApiError struct { + Status int32 + Inner *pb.Error } -func (e *apiError) Error() string { - res := fmt.Sprintf("Status: %d, Kind: %q, Error: %q", e.status, e.inner.GetKind(), e.inner.GetMessage()) +func (e *ApiError) Error() string { + res := fmt.Sprintf("Status: %d, Kind: %q, Error: %q", e.Status, e.Inner.GetKind(), e.Inner.GetMessage()) var dets []string - for _, rf := range e.inner.GetDetails().GetRequestFields() { + for _, rf := range e.Inner.GetDetails().GetRequestFields() { dets = append(dets, fmt.Sprintf("{name: %q, desc: %q}", rf.GetName(), rf.GetDescription())) } if len(dets) > 0 { @@ -39,19 +54,19 @@ func (e *apiError) Error() string { return res } -func (e *apiError) Is(target error) bool { - var tApiErr *apiError +func (e *ApiError) Is(target error) bool { + var tApiErr *ApiError if !errors.As(target, &tApiErr) { return false } - return tApiErr.inner.Kind == e.inner.Kind && tApiErr.status == e.status + return tApiErr.Inner.Kind == e.Inner.Kind && tApiErr.Status == e.Status } // ApiErrorWithCode returns an api error with the provided code. func ApiErrorWithCode(c codes.Code) error { - return &apiError{ - status: int32(runtime.HTTPStatusFromCode(c)), - inner: &pb.Error{ + return &ApiError{ + Status: int32(runtime.HTTPStatusFromCode(c)), + Inner: &pb.Error{ Kind: c.String(), }, } @@ -59,9 +74,9 @@ func ApiErrorWithCode(c codes.Code) error { // ApiErrorWithCodeAndMessage returns an api error with the provided code and message. func ApiErrorWithCodeAndMessage(c codes.Code, msg string, args ...interface{}) error { - return &apiError{ - status: int32(runtime.HTTPStatusFromCode(c)), - inner: &pb.Error{ + return &ApiError{ + Status: int32(runtime.HTTPStatusFromCode(c)), + Inner: &pb.Error{ Kind: c.String(), Message: fmt.Sprintf(msg, args...), }, @@ -70,9 +85,9 @@ func ApiErrorWithCodeAndMessage(c codes.Code, msg string, args ...interface{}) e // NotFoundError returns an ApiError indicating a resource couldn't be found. func NotFoundError() error { - return &apiError{ - status: http.StatusNotFound, - inner: &pb.Error{ + return &ApiError{ + Status: http.StatusNotFound, + Inner: &pb.Error{ Kind: codes.NotFound.String(), Message: "Resource not found.", }, @@ -80,19 +95,19 @@ func NotFoundError() error { } // NotFoundErrorf returns an ApiError indicating a resource couldn't be found. -func NotFoundErrorf(msg string, a ...interface{}) *apiError { - return &apiError{ - status: http.StatusNotFound, - inner: &pb.Error{ +func NotFoundErrorf(msg string, a ...interface{}) *ApiError { + return &ApiError{ + Status: http.StatusNotFound, + Inner: &pb.Error{ Kind: codes.NotFound.String(), Message: fmt.Sprintf(msg, a...), }, } } -var unauthorizedError = &apiError{ - status: http.StatusForbidden, - inner: &pb.Error{ +var unauthorizedError = &ApiError{ + Status: http.StatusForbidden, + Inner: &pb.Error{ Kind: codes.PermissionDenied.String(), Message: "Forbidden.", }, @@ -102,9 +117,9 @@ func ForbiddenError() error { return unauthorizedError } -var unauthenticatedError = &apiError{ - status: http.StatusUnauthorized, - inner: &pb.Error{ +var unauthenticatedError = &ApiError{ + Status: http.StatusUnauthorized, + Inner: &pb.Error{ Kind: codes.Unauthenticated.String(), Message: "Unauthenticated, or invalid token.", }, @@ -114,47 +129,49 @@ func UnauthenticatedError() error { return unauthenticatedError } -func InvalidArgumentErrorf(msg string, fields map[string]string) *apiError { +func InvalidArgumentErrorf(msg string, fields map[string]string) *ApiError { const op = "handlers.InvalidArgumentErrorf" ctx := context.TODO() err := ApiErrorWithCodeAndMessage(codes.InvalidArgument, msg) - var apiErr *apiError + var apiErr *ApiError if !errors.As(err, &apiErr) { event.WriteError(ctx, op, err, event.WithInfoMsg("Unable to build invalid argument api error.")) } if len(fields) > 0 { - apiErr.inner.Details = &pb.ErrorDetails{} + apiErr.Inner.Details = &pb.ErrorDetails{} } for k, v := range fields { - apiErr.inner.Details.RequestFields = append(apiErr.inner.Details.RequestFields, &pb.FieldError{Name: k, Description: v}) + apiErr.Inner.Details.RequestFields = append(apiErr.Inner.Details.RequestFields, &pb.FieldError{Name: k, Description: v}) } - sort.Slice(apiErr.inner.GetDetails().GetRequestFields(), func(i, j int) bool { - return apiErr.inner.Details.RequestFields[i].GetName() < apiErr.inner.Details.RequestFields[j].GetName() + sort.Slice(apiErr.Inner.GetDetails().GetRequestFields(), func(i, j int) bool { + return apiErr.Inner.Details.RequestFields[i].GetName() < apiErr.Inner.Details.RequestFields[j].GetName() }) return apiErr } +var statusRegEx = regexp.MustCompile("Status: ([0-9]+), Kind: \"(.*)\", Error: \"(.*)\"") + // Converts a known errors into an error that can presented to an end user over the API. -func backendErrorToApiError(inErr error) *apiError { +func backendErrorToApiError(inErr error) *ApiError { stErr := status.Convert(inErr) switch { case errors.Is(inErr, runtime.ErrNotMatch): // grpc gateway uses this error when the path was not matched, but the error uses codes.Unimplemented which doesn't match the intention. // Overwrite the error to match our expected behavior. - return &apiError{ - status: http.StatusNotFound, - inner: &pb.Error{ + return &ApiError{ + Status: http.StatusNotFound, + Inner: &pb.Error{ Kind: codes.NotFound.String(), Message: http.StatusText(http.StatusNotFound), }, } case status.Code(inErr) == codes.Unimplemented: // Instead of returning a 501 we always want to return a 405 when a method isn't implemented. - return &apiError{ - status: http.StatusMethodNotAllowed, - inner: &pb.Error{ + return &ApiError{ + Status: http.StatusMethodNotAllowed, + Inner: &pb.Error{ Kind: codes.Unimplemented.String(), Message: stErr.Message(), }, @@ -176,11 +193,23 @@ func backendErrorToApiError(inErr error) *apiError { statusCode = int32(domainErr.Code) } + // perhaps the error is from the grpc gateway, so match against the known + // apiError msg format. + if found := statusRegEx.FindStringSubmatch(stErr.Message()); len(found) == 4 { + u32, err := strconv.ParseInt(found[1], 10, 32) + if err == nil { // notice it's testing for NO err + return &ApiError{ + Status: int32(u32), + Inner: &pb.Error{Kind: found[2], Message: found[3]}, + } + } + } + // TODO: Don't return potentially sensitive information (like which user id an account // is already associated with when attempting to re-associate it). - return &apiError{ - status: statusCode, - inner: &pb.Error{Kind: codes.Internal.String(), Message: inErr.Error()}, + return &ApiError{ + Status: statusCode, + Inner: &pb.Error{Kind: codes.Internal.String(), Message: inErr.Error()}, } } @@ -189,7 +218,70 @@ func ErrorHandler() runtime.ErrorHandlerFunc { const errorFallback = `{"error": "failed to marshal error message"}` return func(ctx context.Context, _ *runtime.ServeMux, mar runtime.Marshaler, w http.ResponseWriter, r *http.Request, inErr error) { // API specified error, otherwise we need to translate repo/db errors. - var apiErr *apiError + + // the grpc server will encoded domain errors into the x-domain-err, so + // let's check there first. (see: controller.errorInterceptor) + md, ok := runtime.ServerMetadataFromContext(ctx) + if ok { + defer func() { + // make sure we don't leak the headers that were used as a comm + // channel between the grpc server and the http proxy + delete(md.HeaderMD, domainErrHeader) + delete(w.Header(), domainErrMetadataHeader) + + delete(md.HeaderMD, apiErrHeader) + delete(w.Header(), apiErrMetadataHeader) + }() + domainErrHdrs := md.HeaderMD.Get(domainErrHeader) + apiErrHdrs := md.HeaderMD.Get(apiErrHeader) + + switch { + case len(domainErrHdrs) > 0: + decoded, err := base58.FastBase58Decoding(domainErrHdrs[0]) + if err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to decode domain err header")) + w.WriteHeader(http.StatusInternalServerError) + if _, err := io.WriteString(w, errorFallback); err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response")) + } + return + } + var pbErr pberrors.Err + if err := proto.Unmarshal(decoded, &pbErr); err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to marshal domain err header")) + w.WriteHeader(http.StatusInternalServerError) + if _, err := io.WriteString(w, errorFallback); err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response")) + } + return + } + inErr = errors.FromPbErrors(&pbErr) + case len(apiErrHdrs) > 0: + decoded, err := base58.FastBase58Decoding(apiErrHdrs[0]) + if err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to decode api err header")) + w.WriteHeader(http.StatusInternalServerError) + if _, err := io.WriteString(w, errorFallback); err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response")) + } + return + } + var pbApiErr pberrors.ApiError + if err := proto.Unmarshal(decoded, &pbApiErr); err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to marshal api err header")) + w.WriteHeader(http.StatusInternalServerError) + if _, err := io.WriteString(w, errorFallback); err != nil { + event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response")) + } + return + } + inErr = &ApiError{ + Status: pbApiErr.Status, + Inner: pbApiErr.ApiError, + } + } + } + var apiErr *ApiError isApiErr := errors.As(inErr, &apiErr) if !isApiErr { if err := backendErrorToApiError(inErr); err != nil && !errors.As(err, &apiErr) { @@ -197,13 +289,13 @@ func ErrorHandler() runtime.ErrorHandlerFunc { } } - if apiErr.status == http.StatusInternalServerError { + if apiErr.Status == http.StatusInternalServerError { event.WriteError(ctx, op, inErr, event.WithInfoMsg("internal error returned")) } - buf, merr := mar.Marshal(apiErr.inner) + buf, merr := mar.Marshal(apiErr.Inner) if merr != nil { - event.WriteError(ctx, op, merr, event.WithInfoMsg("failed to marshal error response", "response", fmt.Sprintf("%#v", apiErr.inner))) + event.WriteError(ctx, op, merr, event.WithInfoMsg("failed to marshal error response", "response", fmt.Sprintf("%#v", apiErr.Inner))) w.WriteHeader(http.StatusInternalServerError) if _, err := io.WriteString(w, errorFallback); err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response")) @@ -211,8 +303,8 @@ func ErrorHandler() runtime.ErrorHandlerFunc { return } - w.Header().Set("Content-Type", mar.ContentType(apiErr.inner)) - w.WriteHeader(int(apiErr.status)) + w.Header().Set("Content-Type", mar.ContentType(apiErr.Inner)) + w.WriteHeader(int(apiErr.Status)) if _, err := w.Write(buf); err != nil { event.WriteError(ctx, op, err, event.WithInfoMsg("failed to send response chunk")) return @@ -221,5 +313,5 @@ func ErrorHandler() runtime.ErrorHandlerFunc { } func ToApiError(e error) *pb.Error { - return backendErrorToApiError(e).inner + return backendErrorToApiError(e).Inner } diff --git a/internal/servers/controller/handlers/errors_test.go b/internal/servers/controller/handlers/errors_test.go index f4b2f34fc..f76a1a51b 100644 --- a/internal/servers/controller/handlers/errors_test.go +++ b/internal/servers/controller/handlers/errors_test.go @@ -32,14 +32,14 @@ func TestApiErrorHandler(t *testing.T) { testCases := []struct { name string err error - expected apiError + expected ApiError }{ { name: "Not Found", err: NotFoundErrorf("Test"), - expected: apiError{ - status: http.StatusNotFound, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusNotFound, + Inner: &pb.Error{ Kind: "NotFound", Message: "Test", }, @@ -51,9 +51,9 @@ func TestApiErrorHandler(t *testing.T) { "k1": "v1", "k2": "v2", }), - expected: apiError{ - status: http.StatusBadRequest, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusBadRequest, + Inner: &pb.Error{ Kind: "InvalidArgument", Message: "Test", Details: &pb.ErrorDetails{ @@ -74,9 +74,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "GrpcGateway Routing Error", err: runtime.ErrNotMatch, - expected: apiError{ - status: http.StatusNotFound, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusNotFound, + Inner: &pb.Error{ Kind: "NotFound", Message: http.StatusText(http.StatusNotFound), }, @@ -85,9 +85,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Unimplemented error", err: status.Error(codes.Unimplemented, "Test"), - expected: apiError{ - status: http.StatusMethodNotAllowed, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusMethodNotAllowed, + Inner: &pb.Error{ Kind: "Unimplemented", Message: "Test", }, @@ -96,9 +96,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Unknown error", err: stderrors.New("Some random error"), - expected: apiError{ - status: http.StatusInternalServerError, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusInternalServerError, + Inner: &pb.Error{ Kind: "Internal", Message: "Some random error", }, @@ -107,9 +107,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error Db invalid public id", err: errors.E(ctx, errors.WithCode(errors.InvalidPublicId)), - expected: apiError{ - status: http.StatusInternalServerError, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusInternalServerError, + Inner: &pb.Error{ Kind: "Internal", Message: "invalid public id, parameter violation: error #102", }, @@ -118,9 +118,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error Db invalid parameter", err: errors.E(ctx, errors.WithCode(errors.InvalidParameter)), - expected: apiError{ - status: http.StatusInternalServerError, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusInternalServerError, + Inner: &pb.Error{ Kind: "Internal", Message: "invalid parameter, parameter violation: error #100", }, @@ -129,9 +129,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error Db invalid field mask", err: errors.E(ctx, errors.WithCode(errors.InvalidFieldMask)), - expected: apiError{ - status: http.StatusBadRequest, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusBadRequest, + Inner: &pb.Error{ Kind: "InvalidArgument", Message: "Error in provided request", Details: &pb.ErrorDetails{RequestFields: []*pb.FieldError{{Name: "update_mask", Description: "Invalid update mask provided."}}}, @@ -141,9 +141,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error Db empty field mask", err: errors.E(ctx, errors.WithCode(errors.EmptyFieldMask)), - expected: apiError{ - status: http.StatusBadRequest, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusBadRequest, + Inner: &pb.Error{ Kind: "InvalidArgument", Message: "Error in provided request", Details: &pb.ErrorDetails{RequestFields: []*pb.FieldError{{Name: "update_mask", Description: "Invalid update mask provided."}}}, @@ -153,9 +153,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error Db not unqiue", err: errors.E(ctx, errors.WithCode(errors.NotUnique)), - expected: apiError{ - status: http.StatusBadRequest, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusBadRequest, + Inner: &pb.Error{ Kind: "InvalidArgument", Message: genericUniquenessMsg, }, @@ -164,9 +164,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error Db record not found", err: errors.E(ctx, errors.WithCode(errors.RecordNotFound)), - expected: apiError{ - status: http.StatusNotFound, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusNotFound, + Inner: &pb.Error{ Kind: "NotFound", Message: genericNotFoundMsg, }, @@ -175,9 +175,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error Db multiple records", err: errors.E(ctx, errors.WithCode(errors.MultipleRecords)), - expected: apiError{ - status: http.StatusInternalServerError, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusInternalServerError, + Inner: &pb.Error{ Kind: "Internal", Message: "multiple records, search issue: error #1101", }, @@ -186,9 +186,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Domain error account already associated", err: errors.E(ctx, errors.WithCode(errors.AccountAlreadyAssociated)), - expected: apiError{ - status: http.StatusBadRequest, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusBadRequest, + Inner: &pb.Error{ Kind: "InvalidArgument", Message: "account already associated with another user, parameter violation: error #114", }, @@ -197,9 +197,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Wrapped domain error", err: errors.E(ctx, errors.WithCode(errors.InvalidAddress), errors.WithMsg("test msg"), errors.WithWrap(errors.E(ctx, errors.WithCode(errors.NotNull), errors.WithMsg("inner msg")))), - expected: apiError{ - status: http.StatusInternalServerError, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusInternalServerError, + Inner: &pb.Error{ Kind: "Internal", Message: "test msg: parameter violation: error #101: inner msg: integrity violation: error #1001", }, @@ -208,9 +208,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Forbidden domain error", err: errors.E(ctx, errors.WithCode(errors.Forbidden), errors.WithMsg("test msg")), - expected: apiError{ - status: http.StatusForbidden, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusForbidden, + Inner: &pb.Error{ Kind: "Internal", Message: "test msg: unknown: error #403", }, @@ -219,9 +219,9 @@ func TestApiErrorHandler(t *testing.T) { { name: "Wrapped forbidden domain error", err: fmt.Errorf("got error: %w", errors.E(ctx, errors.WithCode(errors.Forbidden), errors.WithMsg("test msg"))), - expected: apiError{ - status: http.StatusForbidden, - inner: &pb.Error{ + expected: ApiError{ + Status: http.StatusForbidden, + Inner: &pb.Error{ Kind: "Internal", Message: "got error: test msg: unknown: error #403", }, @@ -234,7 +234,7 @@ func TestApiErrorHandler(t *testing.T) { w := httptest.NewRecorder() tested(ctx, mux, outMarsh, w, req, tc.err) resp := w.Result() - assert.EqualValues(tc.expected.status, resp.StatusCode) + assert.EqualValues(tc.expected.Status, resp.StatusCode) got, err := ioutil.ReadAll(resp.Body) require.NoError(err) @@ -243,8 +243,8 @@ func TestApiErrorHandler(t *testing.T) { err = inMarsh.Unmarshal(got, gotErr) require.NoError(err) - assert.Equal(tc.expected.status, int32(resp.StatusCode)) - assert.Empty(cmp.Diff(tc.expected.inner, gotErr, protocmp.Transform())) + assert.Equal(tc.expected.Status, int32(resp.StatusCode)) + assert.Empty(cmp.Diff(tc.expected.Inner, gotErr, protocmp.Transform())) }) } } diff --git a/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go b/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go index 7c468be33..1ca2cda25 100644 --- a/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go +++ b/internal/servers/controller/handlers/managed_groups/managed_group_service_test.go @@ -417,6 +417,7 @@ func TestDelete_twice(t *testing.T) { o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase) + require.NoError(err) oidcAm := oidc.TestAuthMethod( t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState, diff --git a/internal/servers/controller/handlers/outgoing_interceptor_test.go b/internal/servers/controller/handlers/outgoing_interceptor_test.go deleted file mode 100644 index 798368596..000000000 --- a/internal/servers/controller/handlers/outgoing_interceptor_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package handlers - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" - pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/structpb" -) - -func TestOutgoingSplitCookie(t *testing.T) { - rec := httptest.NewRecorder() - attrs, err := ProtoToStruct(&pb.AuthToken{Token: "t_abc_1234567890"}) - require.NoError(t, err) - attrs.GetFields()["token_type"] = structpb.NewStringValue("cookie") - require.NoError(t, OutgoingInterceptor(context.Background(), rec, &pbs.AuthenticateResponse{Attributes: attrs})) - assert.ElementsMatch(t, rec.Result().Cookies(), []*http.Cookie{ - {Name: HttpOnlyCookieName, Value: "34567890", HttpOnly: true, Raw: "wt-http-token-cookie=34567890; HttpOnly"}, - {Name: JsVisibleCookieName, Value: "t_abc_12", Raw: "wt-js-token-cookie=t_abc_12"}, - }) -} diff --git a/internal/servers/controller/handlers/outgoing_interceptor.go b/internal/servers/controller/handlers/outgoing_response_filter.go similarity index 55% rename from internal/servers/controller/handlers/outgoing_interceptor.go rename to internal/servers/controller/handlers/outgoing_response_filter.go index 2814c2b10..304409775 100644 --- a/internal/servers/controller/handlers/outgoing_interceptor.go +++ b/internal/servers/controller/handlers/outgoing_response_filter.go @@ -4,30 +4,65 @@ import ( "context" "fmt" "net/http" + "strconv" "strings" + "github.com/hashicorp/boundary/internal/errors" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/observability/event" authtokenpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" ) const ( - HttpOnlyCookieName = "wt-http-token-cookie" - JsVisibleCookieName = "wt-js-token-cookie" - tokenTypeField = "token_type" - finalRedirectUrlField = "final_redirect_url" - statusField = "status" + HttpOnlyCookieName = "wt-http-token-cookie" + JsVisibleCookieName = "wt-js-token-cookie" + tokenTypeField = "token_type" + finalRedirectUrlField = "final_redirect_url" + statusField = "status" + StatusCodeHeader = "x-http-code" + statusCodeMetadataHeader = "Grpc-Metadata-X-Http-Code" ) -func OutgoingInterceptor(ctx context.Context, w http.ResponseWriter, m proto.Message) error { - const op = "handlers.OutgoingInterceptor" - m = m.ProtoReflect().Interface() - if !m.ProtoReflect().IsValid() { - // This would be the case if it's a nil pointer - w.WriteHeader(http.StatusNoContent) - return nil +// SetStatusCode allows a grpc service handler to set the outgoing http status +// code +func SetStatusCode(ctx context.Context, code int) error { + const op = "handlers.SetHttpStatusCode" + if http.StatusText(code) == "" { + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown status code: %d", code)) + } + if err := grpc.SetHeader(ctx, metadata.Pairs(StatusCodeHeader, strconv.Itoa(code))); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Internal)) + } + return nil +} + +// OutgoingResponseFilter is a gRPC gateway WithForwardResponseOption. It's +// basically a filter that can manipulate the http response and has acesss to +// the outgoing proto msg +func OutgoingResponseFilter(ctx context.Context, w http.ResponseWriter, m proto.Message) error { + const op = "handlers.OutgoingResponseFilter" + + if md, ok := runtime.ServerMetadataFromContext(ctx); ok { + // set http status codes based on metadata set by the grpc service + if statusCodes := md.HeaderMD.Get(StatusCodeHeader); len(statusCodes) > 0 { + defer func() { + // delete the headers to not expose any grpc-metadata in http response + delete(md.HeaderMD, StatusCodeHeader) + delete(w.Header(), statusCodeMetadataHeader) + }() + lastStatus, err := strconv.Atoi(statusCodes[len(statusCodes)-1]) + if err != nil { + return fmt.Errorf("%s: unable to convert status code %s: %w", op, statusCodes[len(statusCodes)-1], err) + } + w.WriteHeader(lastStatus) + return nil + } } // add the outgoing resp proto. See: common.flushGatedEvents is where the diff --git a/internal/servers/controller/handlers/outgoing_response_filter_test.go b/internal/servers/controller/handlers/outgoing_response_filter_test.go new file mode 100644 index 000000000..c445329d3 --- /dev/null +++ b/internal/servers/controller/handlers/outgoing_response_filter_test.go @@ -0,0 +1,71 @@ +package handlers + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + emptypb "github.com/hashicorp/boundary/internal/gen/controller/api" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +func TestOutgoingSplitCookie(t *testing.T) { + rec := httptest.NewRecorder() + attrs, err := ProtoToStruct(&pb.AuthToken{Token: "t_abc_1234567890"}) + require.NoError(t, err) + attrs.GetFields()["token_type"] = structpb.NewStringValue("cookie") + require.NoError(t, OutgoingResponseFilter(context.Background(), rec, &pbs.AuthenticateResponse{Attributes: attrs})) + assert.ElementsMatch(t, rec.Result().Cookies(), []*http.Cookie{ + {Name: HttpOnlyCookieName, Value: "34567890", HttpOnly: true, Raw: "wt-http-token-cookie=34567890; HttpOnly"}, + {Name: JsVisibleCookieName, Value: "t_abc_12", Raw: "wt-js-token-cookie=t_abc_12"}, + }) +} + +func TestOutgoingResponseFilter_StatusCode(t *testing.T) { + tests := []struct { + name string + md runtime.ServerMetadata + msg proto.Message + wantErr bool + wantStatusCode int + wantContent bool + }{ + { + name: "204", + md: runtime.ServerMetadata{HeaderMD: metadata.Pairs(StatusCodeHeader, "204")}, + wantStatusCode: 204, + }, + { + name: "418", + md: runtime.ServerMetadata{HeaderMD: metadata.Pairs(StatusCodeHeader, "418")}, + wantStatusCode: 418, + wantContent: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + rec := httptest.NewRecorder() + ctx := runtime.NewServerMetadataContext(context.Background(), tt.md) + err := OutgoingResponseFilter(ctx, rec, &emptypb.EmptyResponse{}) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + resp := rec.Result() + if tt.wantStatusCode != 0 { + assert.Equal(tt.wantStatusCode, resp.StatusCode) + } + }) + } +} diff --git a/internal/servers/controller/handlers/sessions/session_service_test.go b/internal/servers/controller/handlers/sessions/session_service_test.go index 5f44e671f..4fa363167 100644 --- a/internal/servers/controller/handlers/sessions/session_service_test.go +++ b/internal/servers/controller/handlers/sessions/session_service_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" @@ -21,6 +22,7 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller/handlers/sessions" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/sessions" @@ -57,7 +59,7 @@ func TestGetSession(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, p.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, p.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) sess := session.TestSession(t, conn, wrap, session.ComposedOf{ UserId: uId, @@ -86,7 +88,7 @@ func TestGetSession(t *testing.T) { Scope: &scopes.ScopeInfo{Id: p.GetPublicId(), Type: scope.Project.String(), ParentScopeId: o.GetPublicId()}, States: []*pb.SessionState{{Status: session.StatusPending.String(), StartTime: sess.CreateTime.GetTimestamp()}}, Certificate: sess.Certificate, - Type: target.TcpSubtype.String(), + Type: tcp.Subtype.String(), AuthorizedActions: testAuthorizedActions, } @@ -174,7 +176,7 @@ func TestList_Self(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, pWithSessions.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, pWithSessions.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) // By default a user can read/cancel their own sessions. session.TestSession(t, conn, wrap, session.ComposedOf{ @@ -210,15 +212,15 @@ func TestList_Self(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup the auth request information req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/sessions?scope_id=%s", pWithSessions.GetPublicId()), nil) - requestInfo := auth.RequestInfo{ + requestInfo := authpb.RequestInfo{ Path: req.URL.Path, Method: req.Method, - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: tc.requester.GetPublicId(), Token: tc.requester.GetToken(), } - ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo) + ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := s.ListSessions(ctx, &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId()}) require.NoError(t, err) assert.Equal(t, tc.count, len(got.GetItems()), got.GetItems()) @@ -258,13 +260,13 @@ func TestList(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, pWithSessions.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, pWithSessions.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) hcOther := static.TestCatalogs(t, conn, pWithOtherSessions.GetPublicId(), 1)[0] hsOther := static.TestSets(t, conn, hcOther.GetPublicId(), 1)[0] hOther := static.TestHosts(t, conn, hcOther.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hsOther.GetPublicId(), []*static.Host{hOther}) - tarOther := target.TestTcpTarget(t, conn, pWithOtherSessions.GetPublicId(), "test", target.WithHostSources([]string{hsOther.GetPublicId()})) + tarOther := tcp.TestTarget(t, conn, pWithOtherSessions.GetPublicId(), "test", target.WithHostSources([]string{hsOther.GetPublicId()})) var wantSession []*pb.Session var totalSession []*pb.Session @@ -298,7 +300,7 @@ func TestList(t *testing.T) { Status: status, States: states, Certificate: sess.Certificate, - Type: target.TcpSubtype.String(), + Type: tcp.Subtype.String(), AuthorizedActions: testAuthorizedActions, }) @@ -333,7 +335,7 @@ func TestList(t *testing.T) { Status: status, States: states, Certificate: sess.Certificate, - Type: target.TcpSubtype.String(), + Type: tcp.Subtype.String(), AuthorizedActions: testAuthorizedActions, }) } @@ -469,7 +471,7 @@ func TestCancel(t *testing.T) { hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) - tar := target.TestTcpTarget(t, conn, p.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) + tar := tcp.TestTarget(t, conn, p.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()})) sess := session.TestSession(t, conn, wrap, session.ComposedOf{ UserId: uId, @@ -496,7 +498,7 @@ func TestCancel(t *testing.T) { Scope: &scopes.ScopeInfo{Id: p.GetPublicId(), Type: scope.Project.String(), ParentScopeId: o.GetPublicId()}, Status: session.StatusCanceling.String(), Certificate: sess.Certificate, - Type: target.TcpSubtype.String(), + Type: tcp.Subtype.String(), AuthorizedActions: testAuthorizedActions, } diff --git a/internal/servers/controller/handlers/targets/target_service.go b/internal/servers/controller/handlers/targets/target_service.go index f1ab5844f..1b1cecdb6 100644 --- a/internal/servers/controller/handlers/targets/target_service.go +++ b/internal/servers/controller/handlers/targets/target_service.go @@ -30,7 +30,8 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" - "github.com/hashicorp/boundary/internal/target/store" + "github.com/hashicorp/boundary/internal/target/tcp" + tcpStore "github.com/hashicorp/boundary/internal/target/tcp/store" "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" @@ -78,7 +79,10 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.TcpTarget{}}, handlers.MaskSource{&pb.Target{}, &pb.TcpTargetAttributes{}}); err != nil { + if maskManager, err = handlers.NewMaskManager( + handlers.MaskDestination{&tcpStore.Target{}}, + handlers.MaskSource{&pb.Target{}, &pb.TcpTargetAttributes{}}, + ); err != nil { panic(err) } } @@ -1010,7 +1014,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession // Generate the endpoint URL endpointUrl := &url.URL{ - Scheme: t.GetType(), + Scheme: t.GetType().String(), } defaultPort := t.GetDefaultPort() if defaultPort != 0 { @@ -1124,7 +1128,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession TargetId: t.GetPublicId(), Scope: authResults.Scope, CreatedTime: sess.CreateTime.GetTimestamp(), - Type: t.GetType(), + Type: t.GetType().String(), Certificate: sess.Certificate, PrivateKey: privKey, HostId: chosenEndpoint.HostId, @@ -1143,7 +1147,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession TargetId: t.GetPublicId(), Scope: authResults.Scope, CreatedTime: sess.CreateTime.GetTimestamp(), - Type: t.GetType(), + Type: t.GetType().String(), AuthorizationToken: string(encodedMarshaledSad), UserId: authResults.UserId, HostId: chosenEndpoint.HostId, @@ -1194,7 +1198,7 @@ func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Targ if tcpAttrs.GetDefaultPort().GetValue() != 0 { opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue())) } - u, err := target.NewTcpTarget(item.GetScopeId(), opts...) + u, err := tcp.New(item.GetScopeId(), opts...) if err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err) } @@ -1202,7 +1206,7 @@ func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Targ if err != nil { return nil, nil, nil, err } - out, hs, cl, err := repo.CreateTcpTarget(ctx, u) + out, hs, cl, err := repo.CreateTarget(ctx, u) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target")) } @@ -1238,7 +1242,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue())) } version := item.GetVersion() - u, err := target.NewTcpTarget(scopeId, opts...) + u, err := tcp.New(scopeId, opts...) if err != nil { return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err) } @@ -1251,7 +1255,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st if err != nil { return nil, nil, nil, err } - out, hs, cl, rowsUpdated, err := repo.UpdateTcpTarget(ctx, u, version, dbMask) + out, hs, cl, rowsUpdated, err := repo.UpdateTarget(ctx, u, version, dbMask) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update target")) } @@ -1476,7 +1480,7 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou out.ScopeId = in.GetScopeId() } if outputFields.Has(globals.TypeField) { - out.Type = target.TcpTargetType.String() + out.Type = in.GetType().String() } if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" { out.Description = wrapperspb.String(in.GetDescription()) @@ -1594,7 +1598,7 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou // * All required parameters are set // * There are no conflicting parameters provided func validateGetRequest(req *pbs.GetTargetRequest) error { - return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, target.TcpTargetPrefix) + return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, tcp.TargetPrefix) } func validateCreateRequest(req *pbs.CreateTargetRequest) error { @@ -1619,7 +1623,7 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error { badFields[globals.SessionMaxSecondsField] = "This must be greater than zero." } switch target.SubtypeFromType(req.GetItem().GetType()) { - case target.TcpSubtype: + case tcp.Subtype: tcpAttrs := &pb.TcpTargetAttributes{} if err := handlers.StructToProto(req.GetItem().GetAttributes(), tcpAttrs); err != nil { badFields[globals.AttributesField] = "Attribute fields do not match the expected format." @@ -1628,11 +1632,9 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error { badFields["attributes.default_port"] = "This optional field cannot be set to 0." } } - switch req.GetItem().GetType() { - case target.TcpTargetType.String(): - case "": + if req.GetItem().GetType() == "" { badFields[globals.TypeField] = "This is a required field." - default: + } else if target.SubtypeFromType(req.GetItem().GetType()) == "" { badFields[globals.TypeField] = "Unknown type provided." } if filter := req.GetItem().GetWorkerFilter(); filter != nil { @@ -1663,8 +1665,8 @@ func validateUpdateRequest(req *pbs.UpdateTargetRequest) error { badFields[globals.SessionMaxSecondsField] = "This must be greater than zero." } switch target.SubtypeFromId(req.GetItem().GetType()) { - case target.TcpSubtype: - if req.GetItem().GetType() != "" && target.SubtypeFromType(req.GetItem().GetType()) != target.TcpSubtype { + case tcp.Subtype: + if req.GetItem().GetType() != "" && target.SubtypeFromType(req.GetItem().GetType()) != tcp.Subtype { badFields[globals.TypeField] = "Cannot modify the resource type." } tcpAttrs := &pb.TcpTargetAttributes{} @@ -1681,11 +1683,11 @@ func validateUpdateRequest(req *pbs.UpdateTargetRequest) error { } } return badFields - }, target.TcpTargetPrefix) + }, tcp.TargetPrefix) } func validateDeleteRequest(req *pbs.DeleteTargetRequest) error { - return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, target.TcpTargetPrefix) + return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, tcp.TargetPrefix) } func validateListRequest(req *pbs.ListTargetsRequest) error { @@ -1705,7 +1707,7 @@ func validateListRequest(req *pbs.ListTargetsRequest) error { func validateAddSetsRequest(req *pbs.AddTargetHostSetsRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1729,7 +1731,7 @@ func validateAddSetsRequest(req *pbs.AddTargetHostSetsRequest) error { func validateSetSetsRequest(req *pbs.SetTargetHostSetsRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1750,7 +1752,7 @@ func validateSetSetsRequest(req *pbs.SetTargetHostSetsRequest) error { func validateRemoveSetsRequest(req *pbs.RemoveTargetHostSetsRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1774,7 +1776,7 @@ func validateRemoveSetsRequest(req *pbs.RemoveTargetHostSetsRequest) error { func validateAddHostSourcesRequest(req *pbs.AddTargetHostSourcesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1798,7 +1800,7 @@ func validateAddHostSourcesRequest(req *pbs.AddTargetHostSourcesRequest) error { func validateSetHostSourcesRequest(req *pbs.SetTargetHostSourcesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1819,7 +1821,7 @@ func validateSetHostSourcesRequest(req *pbs.SetTargetHostSourcesRequest) error { func validateRemoveHostSourcesRequest(req *pbs.RemoveTargetHostSourcesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1843,7 +1845,7 @@ func validateRemoveHostSourcesRequest(req *pbs.RemoveTargetHostSourcesRequest) e func validateAddLibrariesRequest(req *pbs.AddTargetCredentialLibrariesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1866,7 +1868,7 @@ func validateAddLibrariesRequest(req *pbs.AddTargetCredentialLibrariesRequest) e func validateSetLibrariesRequest(req *pbs.SetTargetCredentialLibrariesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1886,7 +1888,7 @@ func validateSetLibrariesRequest(req *pbs.SetTargetCredentialLibrariesRequest) e func validateRemoveLibrariesRequest(req *pbs.RemoveTargetCredentialLibrariesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1909,7 +1911,7 @@ func validateRemoveLibrariesRequest(req *pbs.RemoveTargetCredentialLibrariesRequ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1932,7 +1934,7 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1952,7 +1954,7 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourcesRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { @@ -1979,7 +1981,7 @@ func validateAuthorizeSessionRequest(req *pbs.AuthorizeSessionRequest) error { scopeIdEmpty := req.GetScopeId() == "" scopeNameEmpty := req.GetScopeName() == "" if nameEmpty { - if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + if !handlers.ValidId(handlers.Id(req.GetId()), tcp.TargetPrefix) { badFields[globals.IdField] = "Incorrectly formatted identifier." } if !scopeIdEmpty { diff --git a/internal/servers/controller/handlers/targets/target_service_test.go b/internal/servers/controller/handlers/targets/target_service_test.go index 778b208b1..58df00e3b 100644 --- a/internal/servers/controller/handlers/targets/target_service_test.go +++ b/internal/servers/controller/handlers/targets/target_service_test.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" spbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services" "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" @@ -34,6 +35,7 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller/handlers/workers" "github.com/hashicorp/boundary/internal/session" "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" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" @@ -112,7 +114,7 @@ func TestGet(t *testing.T) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})) + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})) pTar := &pb.Target{ Id: tar.GetPublicId(), @@ -121,7 +123,7 @@ func TestGet(t *testing.T) { CreatedTime: tar.CreateTime.GetTimestamp(), UpdatedTime: tar.UpdateTime.GetTimestamp(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: o.GetPublicId()}, - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), HostSetIds: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, HostSourceIds: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, Attributes: new(structpb.Struct), @@ -147,7 +149,7 @@ func TestGet(t *testing.T) { }, { name: "Get a non existing Target", - req: &pbs.GetTargetRequest{Id: target.TcpTargetPrefix + "_DoesntExis"}, + req: &pbs.GetTargetRequest{Id: tcp.TargetPrefix + "_DoesntExis"}, res: nil, err: handlers.ApiErrorWithCode(codes.NotFound), }, @@ -159,7 +161,7 @@ func TestGet(t *testing.T) { }, { name: "space in id", - req: &pbs.GetTargetRequest{Id: target.TcpTargetPrefix + "_1 23456789"}, + req: &pbs.GetTargetRequest{Id: tcp.TargetPrefix + "_1 23456789"}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, @@ -207,7 +209,7 @@ func TestList(t *testing.T) { var totalTars []*pb.Target for i := 0; i < 5; i++ { name := fmt.Sprintf("tar%d", i) - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), name, target.WithHostSources([]string{hss[0].GetPublicId(), hss[1].GetPublicId()})) + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), name, target.WithHostSources([]string{hss[0].GetPublicId(), hss[1].GetPublicId()})) wantTars = append(wantTars, &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj.GetPublicId(), @@ -216,14 +218,14 @@ func TestList(t *testing.T) { CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: new(structpb.Struct), SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(1), AuthorizedActions: testAuthorizedActions, }) totalTars = append(totalTars, wantTars[i]) - tar = target.TestTcpTarget(t, conn, otherProj.GetPublicId(), name, target.WithHostSources([]string{otherHss[0].GetPublicId(), otherHss[1].GetPublicId()})) + tar = tcp.TestTarget(t, conn, otherProj.GetPublicId(), name, target.WithHostSources([]string{otherHss[0].GetPublicId(), otherHss[1].GetPublicId()})) totalTars = append(totalTars, &pb.Target{ Id: tar.GetPublicId(), ScopeId: otherProj.GetPublicId(), @@ -232,7 +234,7 @@ func TestList(t *testing.T) { CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: new(structpb.Struct), SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(1), @@ -322,7 +324,7 @@ func TestDelete(t *testing.T) { } _, proj := iam.TestScopes(t, iamRepo) - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") s, err := testService(t, conn, kms, wrapper) require.NoError(t, err, "Couldn't create a new target service.") @@ -345,7 +347,7 @@ func TestDelete(t *testing.T) { name: "Delete Not Existing Target", scopeId: proj.GetPublicId(), req: &pbs.DeleteTargetRequest{ - Id: target.TcpTargetPrefix + "_doesntexis", + Id: tcp.TargetPrefix + "_doesntexis", }, err: handlers.ApiErrorWithCode(codes.NotFound), }, @@ -353,7 +355,7 @@ func TestDelete(t *testing.T) { name: "Bad target id formatting", scopeId: proj.GetPublicId(), req: &pbs.DeleteTargetRequest{ - Id: target.TcpTargetPrefix + "_bad_format", + Id: tcp.TargetPrefix + "_bad_format", }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, @@ -385,7 +387,7 @@ func TestDelete_twice(t *testing.T) { _, proj := iam.TestScopes(t, iamRepo) - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") s, err := testService(t, conn, kms, wrapper) require.NoError(err, "Couldn't create a new target service.") @@ -425,20 +427,20 @@ func TestCreate(t *testing.T) { ScopeId: proj.GetPublicId(), Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(2), }}, WorkerFilter: wrapperspb.String(`type == "bar"`), }}, res: &pbs.CreateTargetResponse{ - Uri: fmt.Sprintf("targets/%s_", target.TcpTargetPrefix), + Uri: fmt.Sprintf("targets/%s_", tcp.TargetPrefix), Item: &pb.Target{ ScopeId: proj.GetPublicId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(2), }}, @@ -454,7 +456,7 @@ func TestCreate(t *testing.T) { req: &pbs.CreateTargetRequest{Item: &pb.Target{ Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(0), }}, @@ -528,7 +530,7 @@ func TestCreate(t *testing.T) { if got != nil { assert.Contains(got.GetUri(), tc.res.GetUri()) - assert.True(strings.HasPrefix(got.GetItem().GetId(), target.TcpTargetPrefix), got.GetItem().GetId()) + assert.True(strings.HasPrefix(got.GetItem().GetId(), tcp.TargetPrefix), got.GetItem().GetId()) // Clear all values which are hard to compare against. got.Uri, tc.res.Uri = "", "" @@ -576,18 +578,18 @@ func TestUpdate(t *testing.T) { {Id: hs[1].GetPublicId(), HostCatalogId: hs[1].GetCatalogId()}, } - tar, err := target.NewTcpTarget(proj.GetPublicId(), target.WithName("default"), target.WithDescription("default")) + tar, err := tcp.New(proj.GetPublicId(), target.WithName("default"), target.WithDescription("default")) tar.DefaultPort = 2 require.NoError(t, err) - gtar, _, _, err := repo.CreateTcpTarget(context.Background(), tar, target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})) + gtar, _, _, err := repo.CreateTarget(context.Background(), tar, target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})) require.NoError(t, err) - tar = gtar.(*target.TcpTarget) + tar = gtar.(*tcp.Target) var version uint32 = 1 resetTarget := func() { version++ - _, _, _, _, err = repo.UpdateTcpTarget(context.Background(), tar, version, []string{"Name", "Description"}) + _, _, _, _, err = repo.UpdateTarget(context.Background(), tar, version, []string{"Name", "Description"}) require.NoError(t, err, "Failed to reset target.") version++ } @@ -617,7 +619,7 @@ func TestUpdate(t *testing.T) { Description: wrapperspb.String("desc"), SessionMaxSeconds: wrapperspb.UInt32(3600), SessionConnectionLimit: wrapperspb.Int32(5), - Type: target.TcpSubtype.String(), + Type: tcp.Subtype.String(), }, }, res: &pbs.UpdateTargetResponse{ @@ -627,7 +629,7 @@ func TestUpdate(t *testing.T) { Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(2), }}, @@ -651,7 +653,7 @@ func TestUpdate(t *testing.T) { Item: &pb.Target{ Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), - Type: target.TcpSubtype.String(), + Type: tcp.Subtype.String(), }, }, res: &pbs.UpdateTargetResponse{ @@ -662,7 +664,7 @@ func TestUpdate(t *testing.T) { Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), CreatedTime: tar.GetCreateTime().GetTimestamp(), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(2), }}, @@ -749,7 +751,7 @@ func TestUpdate(t *testing.T) { Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("default"), CreatedTime: tar.GetCreateTime().GetTimestamp(), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(2), }}, @@ -782,7 +784,7 @@ func TestUpdate(t *testing.T) { Name: wrapperspb.String("updated"), Description: wrapperspb.String("default"), CreatedTime: tar.GetCreateTime().GetTimestamp(), - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(2), }}, @@ -818,7 +820,7 @@ func TestUpdate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "default_port": structpb.NewNumberValue(2), }}, - Type: target.TcpTargetType.String(), + Type: tcp.Subtype.String(), HostSetIds: hsIds, HostSets: hostSets, HostSourceIds: hostSourceIds, @@ -832,7 +834,7 @@ func TestUpdate(t *testing.T) { { name: "Update a Non Existing Target", req: &pbs.UpdateTargetRequest{ - Id: target.TcpTargetPrefix + "_DoesntExis", + Id: tcp.TargetPrefix + "_DoesntExis", UpdateMask: &field_mask.FieldMask{ Paths: []string{"description"}, }, @@ -944,10 +946,10 @@ func TestUpdate_BadVersion(t *testing.T) { repo, err := repoFn() require.NoError(t, err, "Couldn't create new target repo.") - tar, err := target.NewTcpTarget(proj.GetPublicId(), target.WithName("default"), target.WithDescription("default")) + tar, err := tcp.New(proj.GetPublicId(), target.WithName("default"), target.WithDescription("default")) tar.DefaultPort = 2 require.NoError(t, err) - gtar, _, _, err := repo.CreateTcpTarget(context.Background(), tar) + gtar, _, _, err := repo.CreateTarget(context.Background(), tar) require.NoError(t, err) tested, err := testService(t, conn, kms, wrapper) @@ -992,37 +994,37 @@ func TestAddTargetHostSets(t *testing.T) { addCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target addHostSets []string resultHostSets []string }{ { name: "Add set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), addHostSets: []string{hs[1].GetPublicId()}, resultHostSets: []string{hs[1].GetPublicId()}, }, { name: "Add set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSets: []string{hs[1].GetPublicId()}, resultHostSets: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, { name: "Add duplicated sets on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSets: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSets: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, { name: "Add plugin set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "plugin empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin empty"), addHostSets: []string{pluginHs.GetPublicId()}, resultHostSets: []string{pluginHs.GetPublicId()}, }, { name: "Add plugin set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSets: []string{pluginHs.GetPublicId()}, resultHostSets: []string{hs[0].GetPublicId(), pluginHs.GetPublicId()}, }, @@ -1045,7 +1047,7 @@ func TestAddTargetHostSets(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") failCases := []struct { name string @@ -1126,43 +1128,43 @@ func TestSetTargetHostSets(t *testing.T) { setCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target setHostSets []string resultHostSets []string }{ { name: "Set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), setHostSets: []string{hs[1].GetPublicId()}, resultHostSets: []string{hs[1].GetPublicId()}, }, { name: "Set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSets: []string{hs[1].GetPublicId()}, resultHostSets: []string{hs[1].GetPublicId()}, }, { name: "Set duplicate host set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSets: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSets: []string{hs[1].GetPublicId()}, }, { name: "Set empty on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "another populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSets: []string{}, resultHostSets: nil, }, { name: "Set plugin set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "plugin empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin empty"), setHostSets: []string{pluginHs.GetPublicId()}, resultHostSets: []string{pluginHs.GetPublicId()}, }, { name: "Set plugin set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSets: []string{pluginHs.GetPublicId()}, resultHostSets: []string{pluginHs.GetPublicId()}, }, @@ -1181,7 +1183,7 @@ func TestSetTargetHostSets(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test name") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test name") failCases := []struct { name string @@ -1254,38 +1256,38 @@ func TestRemoveTargetHostSets(t *testing.T) { removeCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target removeHosts []string resultHosts []string wantErr bool }{ { name: "Remove from empty", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), removeHosts: []string{hs[1].GetPublicId()}, wantErr: true, }, { name: "Remove 1 of 2 sets", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHosts: []string{hs[1].GetPublicId()}, resultHosts: []string{hs[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 sets", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHosts: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHosts: []string{hs[0].GetPublicId()}, }, { name: "Remove all hosts from set", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove all", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHosts: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, resultHosts: []string{}, }, { name: "Remove 1 plugin of 2 sets", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove plugin partial", target.WithHostSources([]string{hs[0].GetPublicId(), pluginHs.GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove plugin partial", target.WithHostSources([]string{hs[0].GetPublicId(), pluginHs.GetPublicId()})), removeHosts: []string{pluginHs.GetPublicId()}, resultHosts: []string{hs[0].GetPublicId()}, }, @@ -1312,7 +1314,7 @@ func TestRemoveTargetHostSets(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "testing") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "testing") failCases := []struct { name string @@ -1394,37 +1396,37 @@ func TestAddTargetHostSources(t *testing.T) { addCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target addHostSources []string resultHostSources []string }{ { name: "Add set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), addHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Add set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, { name: "Add duplicated sets on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSources: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, { name: "Add plugin set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "plugin empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin empty"), addHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{pluginHs.GetPublicId()}, }, { name: "Add plugin set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId(), pluginHs.GetPublicId()}, }, @@ -1447,7 +1449,7 @@ func TestAddTargetHostSources(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") failCases := []struct { name string @@ -1528,37 +1530,37 @@ func TestSetTargetHostSources(t *testing.T) { setCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target setHostSources []string resultHostSources []string }{ { name: "Set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), setHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Set plugin set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{pluginHs.GetPublicId()}, }, { name: "Set duplicate host set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Set empty on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "another populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{}, resultHostSources: nil, }, @@ -1577,7 +1579,7 @@ func TestSetTargetHostSources(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test name") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test name") failCases := []struct { name string @@ -1650,38 +1652,38 @@ func TestRemoveTargetHostSources(t *testing.T) { removeCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target removeHostSources []string resultHostSources []string wantErr bool }{ { name: "Remove from empty", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), removeHostSources: []string{hs[1].GetPublicId()}, wantErr: true, }, { name: "Remove 1 of 2 sets", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId()}, }, { name: "Remove 1 plugin set of 2 sets", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove plugin partial", target.WithHostSources([]string{hs[0].GetPublicId(), pluginHs.GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove plugin partial", target.WithHostSources([]string{hs[0].GetPublicId(), pluginHs.GetPublicId()})), removeHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 sets", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHostSources: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId()}, }, { name: "Remove all hosts from set", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove all", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHostSources: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{}, }, @@ -1708,7 +1710,7 @@ func TestRemoveTargetHostSources(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "testing") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "testing") failCases := []struct { name string @@ -1784,25 +1786,25 @@ func TestAddTargetLibraries(t *testing.T) { addCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target addLibraries []string resultLibraryIds []string }{ { name: "Add library on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty for libraries"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty for libraries"), addLibraries: []string{cls[1].GetPublicId()}, resultLibraryIds: []string{cls[1].GetPublicId()}, }, { name: "Add library on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated for libraries", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated for libraries", target.WithCredentialSources([]string{cls[0].GetPublicId()})), addLibraries: []string{cls[1].GetPublicId()}, resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, { name: "Add duplicated libraries on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicated for libraries", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicated for libraries", target.WithCredentialSources([]string{cls[0].GetPublicId()})), addLibraries: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, @@ -1833,7 +1835,7 @@ func TestAddTargetLibraries(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") failCases := []struct { name string @@ -1919,32 +1921,32 @@ func TestSetTargetLibraries(t *testing.T) { setCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target setLibraries []string resultLibraryIds []string resultLibraries []*pb.CredentialLibrary }{ { name: "Set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), setLibraries: []string{cls[1].GetPublicId()}, resultLibraryIds: []string{cls[1].GetPublicId()}, }, { name: "Set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), setLibraries: []string{cls[1].GetPublicId()}, resultLibraryIds: []string{cls[1].GetPublicId()}, }, { name: "Set duplicate libraries on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId()})), setLibraries: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultLibraryIds: []string{cls[1].GetPublicId()}, }, { name: "Set empty on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), setLibraries: []string{}, resultLibraryIds: nil, }, @@ -1978,7 +1980,7 @@ func TestSetTargetLibraries(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test name") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test name") failCases := []struct { name string @@ -2045,26 +2047,26 @@ func TestRemoveTargetLibraries(t *testing.T) { removeCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target removeLibs []string resultLibIds []string wantErr bool }{ { name: "Remove from empty", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), removeLibs: []string{cls[1].GetPublicId()}, wantErr: true, }, { name: "Remove 1 of 2 libraries", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), removeLibs: []string{cls[1].GetPublicId()}, resultLibIds: []string{cls[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 libraries", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), removeLibs: []string{ cls[1].GetPublicId(), cls[1].GetPublicId(), }, @@ -2072,7 +2074,7 @@ func TestRemoveTargetLibraries(t *testing.T) { }, { name: "Remove all libraries from target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), removeLibs: []string{ cls[0].GetPublicId(), cls[1].GetPublicId(), }, @@ -2099,7 +2101,7 @@ func TestRemoveTargetLibraries(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "testing") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "testing") failCases := []struct { name string @@ -2181,25 +2183,25 @@ func TestAddTargetSources(t *testing.T) { addCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target addSources []string resultSourceIds []string }{ { name: "Add source on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty for sources"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty for sources"), addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Add source on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated for sources", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated for sources", target.WithCredentialSources([]string{cls[0].GetPublicId()})), addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, { name: "Add duplicated sources on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicated for sources", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicated for sources", target.WithCredentialSources([]string{cls[0].GetPublicId()})), addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultSourceIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, @@ -2230,7 +2232,7 @@ func TestAddTargetSources(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") failCases := []struct { name string @@ -2316,32 +2318,32 @@ func TestSetTargetCredentialSources(t *testing.T) { setCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target setCredentialSources []string resultCredentialSourceIds []string resultCredentialSources []*pb.CredentialLibrary }{ { name: "Set on empty target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set duplicate sources on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId()})), setCredentialSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set empty on populated target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), setCredentialSources: []string{}, resultCredentialSourceIds: nil, }, @@ -2375,7 +2377,7 @@ func TestSetTargetCredentialSources(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test name") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test name") failCases := []struct { name string @@ -2442,26 +2444,26 @@ func TestRemoveTargetCredentialSources(t *testing.T) { removeCases := []struct { name string - tar *target.TcpTarget + tar *tcp.Target removeCredentialSources []string resultCredentialSourceIds []string wantErr bool }{ { name: "Remove from empty", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "empty"), removeCredentialSources: []string{cls[1].GetPublicId()}, wantErr: true, }, { name: "Remove 1 of 2 sources", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), removeCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 sources", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), removeCredentialSources: []string{ cls[1].GetPublicId(), cls[1].GetPublicId(), }, @@ -2469,7 +2471,7 @@ func TestRemoveTargetCredentialSources(t *testing.T) { }, { name: "Remove all sources from target", - tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), removeCredentialSources: []string{ cls[0].GetPublicId(), cls[1].GetPublicId(), }, @@ -2496,7 +2498,7 @@ func TestRemoveTargetCredentialSources(t *testing.T) { }) } - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "testing") + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "testing") failCases := []struct { name string @@ -2622,9 +2624,9 @@ func TestAuthorizeSession(t *testing.T) { atRepoFn, serversRepoFn, kms, - auth.RequestInfo{ + &authpb.RequestInfo{ Token: at.GetToken(), - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), }) @@ -2635,6 +2637,7 @@ func TestAuthorizeSession(t *testing.T) { s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) require.NoError(t, err) + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] shs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] @@ -2687,7 +2690,6 @@ func TestAuthorizeSession(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), tc.name) apiTar, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), @@ -2834,9 +2836,9 @@ func TestAuthorizeSession_Errors(t *testing.T) { atRepoFn, serversRepoFn, kms, - auth.RequestInfo{ + &authpb.RequestInfo{ Token: at.GetToken(), - TokenFormat: auth.AuthTokenTypeBearer, + TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), }) r := iam.TestRole(t, conn, proj.GetPublicId()) @@ -2848,7 +2850,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "database"})) store := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) - workerExists := func(tar *target.TcpTarget) (version uint32) { + workerExists := func(tar *tcp.Target) (version uint32) { workerService := workers.NewWorkerServiceServer(serversRepoFn, sessionRepoFn, &sync.Map{}, kms) _, err := workerService.Status(context.Background(), &spbs.StatusRequest{ Worker: &spb.Server{ @@ -2860,7 +2862,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { return tar.GetVersion() } - hostSetNoHostExists := func(tar *target.TcpTarget) (version uint32) { + hostSetNoHostExists := func(tar *tcp.Target) (version uint32) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] @@ -2873,7 +2875,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { return tr.GetItem().GetVersion() } - hostExists := func(tar *target.TcpTarget) (version uint32) { + hostExists := func(tar *tcp.Target) (version uint32) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] @@ -2887,7 +2889,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { return apiTar.GetItem().GetVersion() } - libraryExists := func(tar *target.TcpTarget) (version uint32) { + libraryExists := func(tar *tcp.Target) (version uint32) { credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) require.NoError(t, err) clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ @@ -2909,7 +2911,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { return tr.GetItem().GetVersion() } - misConfiguredlibraryExists := func(tar *target.TcpTarget) (version uint32) { + misConfiguredlibraryExists := func(tar *tcp.Target) (version uint32) { credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) require.NoError(t, err) clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ @@ -2933,33 +2935,33 @@ func TestAuthorizeSession_Errors(t *testing.T) { cases := []struct { name string - setup []func(*target.TcpTarget) uint32 + setup []func(*tcp.Target) uint32 err bool }{ { // This one must be run first since it relies on the DB not having any worker details name: "no worker", - setup: []func(tcpTarget *target.TcpTarget) uint32{hostExists, libraryExists}, + setup: []func(tcpTarget *tcp.Target) uint32{hostExists, libraryExists}, err: true, }, { name: "success", - setup: []func(tcpTarget *target.TcpTarget) uint32{workerExists, hostExists, libraryExists}, + setup: []func(tcpTarget *tcp.Target) uint32{workerExists, hostExists, libraryExists}, }, { name: "no hosts", - setup: []func(tcpTarget *target.TcpTarget) uint32{workerExists, hostSetNoHostExists, libraryExists}, + setup: []func(tcpTarget *tcp.Target) uint32{workerExists, hostSetNoHostExists, libraryExists}, err: true, }, { name: "bad library configuration", - setup: []func(tcpTarget *target.TcpTarget) uint32{workerExists, hostExists, misConfiguredlibraryExists}, + setup: []func(tcpTarget *tcp.Target) uint32{workerExists, hostExists, misConfiguredlibraryExists}, err: true, }, } for i, tc := range cases { t.Run(tc.name, func(t *testing.T) { - tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), fmt.Sprintf("test-%d", i)) + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), fmt.Sprintf("test-%d", i)) for _, fn := range tc.setup { ver := fn(tar) diff --git a/internal/servers/controller/handlers/verifiers.go b/internal/servers/controller/handlers/verifiers.go index 5481c62ab..4d394ee07 100644 --- a/internal/servers/controller/handlers/verifiers.go +++ b/internal/servers/controller/handlers/verifiers.go @@ -183,7 +183,7 @@ func ValidId(i Id, prefixes ...string) bool { continue } id = strings.TrimPrefix(id, prefix) - if !reInvalidID.Match([]byte(id)) { + if !reInvalidID.MatchString(id) { return true } } diff --git a/internal/servers/controller/handlers/verifiers_test.go b/internal/servers/controller/handlers/verifiers_test.go index d1717c2d6..7fd291b12 100644 --- a/internal/servers/controller/handlers/verifiers_test.go +++ b/internal/servers/controller/handlers/verifiers_test.go @@ -15,10 +15,10 @@ import ( func errorIncludesFields(t *testing.T, err error, wantFields []string) { t.Helper() - var apiErr *apiError + var apiErr *ApiError require.True(t, errors.As(err, &apiErr)) var gotFields []string - for _, d := range apiErr.inner.GetDetails().GetRequestFields() { + for _, d := range apiErr.Inner.GetDetails().GetRequestFields() { gotFields = append(gotFields, d.GetName()) } assert.ElementsMatch(t, gotFields, wantFields) diff --git a/internal/servers/controller/interceptor.go b/internal/servers/controller/interceptor.go new file mode 100644 index 000000000..4dfaeab98 --- /dev/null +++ b/internal/servers/controller/interceptor.go @@ -0,0 +1,207 @@ +package controller + +import ( + "context" + "fmt" + "net/http" + "reflect" + + "github.com/hashicorp/boundary/internal/errors" + pb "github.com/hashicorp/boundary/internal/gen/controller/api" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" + pberrors "github.com/hashicorp/boundary/internal/gen/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/requests" + "github.com/hashicorp/boundary/internal/servers/controller/auth" + "github.com/hashicorp/boundary/internal/servers/controller/common" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + + "github.com/mr-tron/base58" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +const ( + requestInfoMdKey = "request-info" + + // domainErrHeader defines an http header for encoded domain errors from the + // grpc server. + domainErrHeader = "x-domain-err" + + // apiErrHeader defines an http header for encoded api errors from the + // grpc server. + apiErrHeader = "x-api-err" +) + +// requestCtxInterceptor creates an unary server interceptor that pulls grpc +// metadata into a ctx for the request. The metadata must be set in an upstream +// http handler/middleware by marshalling a RequestInfo protobuf into the +// requestInfoMdKey header (see: controller.wrapHandlerWithCommonFuncs). +func requestCtxInterceptor( + ctx context.Context, + iamRepoFn common.IamRepoFactory, + authTokenRepoFn common.AuthTokenRepoFactory, + serversRepoFn common.ServersRepoFactory, + kms *kms.Kms, + ticket string, +) (grpc.UnaryServerInterceptor, error) { + const op = "controller.requestCtxInterceptor" + if iamRepoFn == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing iam repo function") + } + if authTokenRepoFn == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth token repo function") + } + if serversRepoFn == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing servers repo function") + } + if kms == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing kms") + } + if ticket == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing ticket") + } + // Authorization unary interceptor function to handle authorize per RPC call + return func(interceptorCtx context.Context, + req interface{}, + _ *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + md, ok := metadata.FromIncomingContext(interceptorCtx) + if !ok { + return nil, errors.New(interceptorCtx, errors.Internal, op, "No metadata") + } + + values := md.Get(requestInfoMdKey) + if len(values) == 0 { + return nil, errors.New(interceptorCtx, errors.Internal, op, "Missing request metadata") + } + if len(values) > 1 { + return nil, errors.New(interceptorCtx, errors.Internal, op, fmt.Sprintf("expected 1 value for %s metadata and got %d", requestInfoMdKey, len(values))) + } + + decoded, err := base58.FastBase58Decoding(values[0]) + if err != nil { + return nil, errors.Wrap(interceptorCtx, err, op, errors.WithCode(errors.Internal), errors.WithMsg("unable to decode request info")) + } + var requestInfo authpb.RequestInfo + if err := proto.Unmarshal(decoded, &requestInfo); err != nil { + return nil, errors.Wrap(interceptorCtx, err, op, errors.WithCode(errors.Internal), errors.WithMsg("unable to unmarshal request info")) + } + switch { + case requestInfo.Ticket == "": + return nil, errors.New(interceptorCtx, errors.Internal, op, "Invalid context (missing ticket)") + case requestInfo.Ticket != ticket: + return nil, errors.New(interceptorCtx, errors.Internal, op, "Invalid context (bad ticket)") + } + + interceptorCtx = auth.NewVerifierContext(interceptorCtx, iamRepoFn, authTokenRepoFn, serversRepoFn, kms, &requestInfo) + + // Add general request information to the context. The information from + // the auth verifier context is pretty specifically curated to + // authentication/authorization verification so this is more + // general-purpose. + // + // We could use requests.NewRequestContext but this saves an immediate + // lookup. + interceptorCtx = context.WithValue(interceptorCtx, requests.ContextRequestInformationKey, &requests.RequestContext{ + Path: requestInfo.Path, + Method: requestInfo.Method, + }) + + // Calls the handler + h, err := handler(interceptorCtx, req) + + return h, err // not convinced we want to wrap every error and turn them into domain errors... + }, nil +} + +func errorInterceptor( + _ context.Context, +) grpc.UnaryServerInterceptor { + const op = "controller.errorInterceptor" + return func(interceptorCtx context.Context, + req interface{}, + _ *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + + // call the handler... + h, handlerErr := handler(interceptorCtx, req) + + // if there's an error and it's a domain err, then encode it into the + if handlerErr != nil { + var domainErr *errors.Err + isDomainErr := errors.As(handlerErr, &domainErr) + if isDomainErr { + pbErr := errors.ToPbErrors(domainErr) + var buf []byte + var err error + if buf, err = proto.Marshal(pbErr); err != nil { + return h, status.Errorf(codes.Internal, "unable to marshal domain error: %s", err) + } + if err := grpc.SetHeader(interceptorCtx, metadata.Pairs(domainErrHeader, base58.FastBase58Encoding(buf))); err != nil { + return h, status.Errorf(codes.Internal, "unable to set domain error header: %s", err) + } + return h, handlerErr + } + + var apiErr *handlers.ApiError + isApiError := errors.As(handlerErr, &apiErr) + if isApiError { + pbErr := &pberrors.ApiError{ + ApiError: apiErr.Inner, + Status: apiErr.Status, + } + var buf []byte + var err error + if buf, err = proto.Marshal(pbErr); err != nil { + return h, status.Errorf(codes.Internal, "unable to marshal api error: %s", err) + } + if err := grpc.SetHeader(interceptorCtx, metadata.Pairs(apiErrHeader, base58.FastBase58Encoding(buf))); err != nil { + return h, status.Errorf(codes.Internal, "unable to set api error header: %s", err) + } + return h, handlerErr + } + } + return h, handlerErr + } +} + +func statusCodeInterceptor( + _ context.Context, +) grpc.UnaryServerInterceptor { + const op = "controller.statusCodeInterceptor" + return func(interceptorCtx context.Context, + req interface{}, + _ *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + + // call the handler... + h, handlerErr := handler(interceptorCtx, req) + + // if a service handler returns nil, nil then we want to single a 204 + // response to the proxy with no resp msg + if isNil(h) && handlerErr == nil { + if err := handlers.SetStatusCode(interceptorCtx, http.StatusNoContent); err != nil { + return &pb.EmptyResponse{}, err + } + return &pb.EmptyResponse{}, nil + } + + return h, handlerErr + } +} + +func isNil(i interface{}) bool { + if i == nil { + return true + } + switch reflect.TypeOf(i).Kind() { + case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: + return reflect.ValueOf(i).IsNil() + } + return false +} diff --git a/internal/servers/controller/interceptor_test.go b/internal/servers/controller/interceptor_test.go new file mode 100644 index 000000000..f970022c0 --- /dev/null +++ b/internal/servers/controller/interceptor_test.go @@ -0,0 +1,453 @@ +package controller + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + pb_api "github.com/hashicorp/boundary/internal/gen/controller/api" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" + pberrors "github.com/hashicorp/boundary/internal/gen/errors" + "github.com/hashicorp/boundary/internal/gen/testing/interceptor" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/servers" + "github.com/hashicorp/boundary/internal/servers/controller/auth" + "github.com/hashicorp/boundary/internal/servers/controller/common" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "github.com/mr-tron/base58" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/proto" +) + +func Test_requestCtxInterceptor(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + atRepoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(rw, rw, kmsCache) + } + serversRepoFn := func() (*servers.Repository, error) { + return servers.NewRepository(rw, rw, kmsCache) + } + + validGatewayTicket := "valid-ticket" + + o, _ := iam.TestScopes(t, iamRepo) + at := authtoken.TestAuthToken(t, conn, kmsCache, o.GetPublicId()) + encToken, err := authtoken.EncryptToken(context.Background(), kmsCache, o.GetPublicId(), at.GetPublicId(), at.GetToken()) + require.NoError(t, err) + tokValue := at.GetPublicId() + "_" + encToken + + newReqCtx := func(gwTicket string) context.Context { + req := httptest.NewRequest("GET", "http://127.0.0.1/v1/scopes/o_1", nil) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tokValue)) + // Add values for authn/authz checking + requestInfo := authpb.RequestInfo{ + Path: req.URL.Path, + Method: req.Method, + } + requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(context.TODO(), kmsCache, req) + requestInfo.Ticket = gwTicket // allows the grpc-gateway to verify the request info came from it's in-memory companion http proxy + marshalledRequestInfo, err := proto.Marshal(&requestInfo) + require.NoError(t, err) + md := metadata.Pairs(requestInfoMdKey, base58.FastBase58Encoding(marshalledRequestInfo)) + mdCtx := metadata.NewIncomingContext(context.Background(), md) + + md, ok := metadata.FromIncomingContext(mdCtx) + require.True(t, ok) + require.NotNil(t, md) + + return mdCtx + } + + factoryCtx := context.Background() + + returnCtxHandler := func(ctx context.Context, req interface{}) (interface{}, error) { + return ctx, nil + } + + tests := []struct { + name string + requestCtx context.Context + iamRepoFn common.IamRepoFactory + authTokenRepoFn common.AuthTokenRepoFactory + serversRepoFn common.ServersRepoFactory + kms *kms.Kms + ticket string + wantFactoryErr bool + wantFactoryErrMatch *errors.Template + wantFactoryErrContains string + wantRequestErr bool + wantRequestErrMatch *errors.Template + wantRequestErrContains string + }{ + { + name: "missing-iam-repo", + requestCtx: newReqCtx(validGatewayTicket), + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + wantFactoryErr: true, + wantFactoryErrMatch: errors.T(errors.InvalidParameter), + wantFactoryErrContains: "missing iam repo", + }, + { + name: "missing-at-repo", + requestCtx: newReqCtx(validGatewayTicket), + iamRepoFn: iamRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + wantFactoryErr: true, + wantFactoryErrMatch: errors.T(errors.InvalidParameter), + wantFactoryErrContains: "missing auth token repo", + }, + { + name: "missing-servers-repo", + requestCtx: newReqCtx(validGatewayTicket), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + kms: kmsCache, + wantFactoryErr: true, + wantFactoryErrMatch: errors.T(errors.InvalidParameter), + wantFactoryErrContains: "missing servers repo function", + }, + { + name: "missing-kms", + requestCtx: newReqCtx(validGatewayTicket), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + wantFactoryErr: true, + wantFactoryErrMatch: errors.T(errors.InvalidParameter), + wantFactoryErrContains: "missing kms", + }, + { + name: "missing-factory-ticket", + requestCtx: newReqCtx(validGatewayTicket), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + wantFactoryErr: true, + wantFactoryErrMatch: errors.T(errors.InvalidParameter), + wantFactoryErrContains: "missing ticket", + }, + { + name: "missing-metadata", + requestCtx: context.Background(), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: validGatewayTicket, + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "No metadata", + }, + { + name: "missing-request-info-metadata", + requestCtx: func() context.Context { + md := metadata.Pairs("greeter-md", "hello") + return metadata.NewIncomingContext(context.Background(), md) + }(), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: validGatewayTicket, + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "Missing request metadata", + }, + { + name: "too-many-request-info-metadata", + requestCtx: func() context.Context { + md := metadata.Pairs(requestInfoMdKey, "first", requestInfoMdKey, "second") + return metadata.NewIncomingContext(context.Background(), md) + }(), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: validGatewayTicket, + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "expected 1 value", + }, + { + name: "request-info-metadata-not-encoded", + requestCtx: func() context.Context { + md := metadata.Pairs(requestInfoMdKey, "hello") + return metadata.NewIncomingContext(context.Background(), md) + }(), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: validGatewayTicket, + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "unable to decode request info", + }, + { + name: "request-info-metadata-not-proto", + requestCtx: func() context.Context { + md := metadata.Pairs(requestInfoMdKey, base58.FastBase58Encoding([]byte("hello"))) + return metadata.NewIncomingContext(context.Background(), md) + }(), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: validGatewayTicket, + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "unable to unmarshal request info", + }, + { + name: "request-info-metadata-not-proto", + requestCtx: func() context.Context { + md := metadata.Pairs(requestInfoMdKey, base58.FastBase58Encoding([]byte("hello"))) + return metadata.NewIncomingContext(context.Background(), md) + }(), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: validGatewayTicket, + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "unable to unmarshal request info", + }, + { + name: "missing-request-ticket", + requestCtx: newReqCtx(""), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: "validGatewayTicket", + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "Invalid context (missing ticket)", + }, + { + name: "bad-ticket", + requestCtx: newReqCtx("bad-ticket"), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: "validGatewayTicket", + wantRequestErr: true, + wantRequestErrMatch: errors.T(errors.Internal), + wantRequestErrContains: "Invalid context (bad ticket)", + }, + { + name: "valid", + requestCtx: newReqCtx(validGatewayTicket), + iamRepoFn: iamRepoFn, + authTokenRepoFn: atRepoFn, + serversRepoFn: serversRepoFn, + kms: kmsCache, + ticket: validGatewayTicket, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + interceptor, err := requestCtxInterceptor(factoryCtx, tt.iamRepoFn, tt.authTokenRepoFn, tt.serversRepoFn, tt.kms, tt.ticket) + if tt.wantFactoryErr { + require.Error(err) + assert.Nil(interceptor) + if tt.wantFactoryErrMatch != nil { + assert.Truef(errors.Match(tt.wantFactoryErrMatch, err), "want err code: %q got: %q", tt.wantFactoryErrMatch.Code, err) + } + if tt.wantFactoryErrContains != "" { + assert.Contains(err.Error(), tt.wantFactoryErrContains) + } + return + } + require.NoError(err) + assert.NotNil(interceptor) + + info := &grpc.UnaryServerInfo{ + FullMethod: "FakeMethod", + } + retCtx, err := interceptor(tt.requestCtx, nil, info, returnCtxHandler) + if tt.wantRequestErr { + require.Error(err) + assert.Nil(retCtx) + if tt.wantRequestErrMatch != nil { + assert.Truef(errors.Match(tt.wantRequestErrMatch, err), "want err code: %q got: %q", tt.wantRequestErrMatch.Code, err) + } + if tt.wantRequestErrContains != "" { + assert.Contains(err.Error(), tt.wantRequestErrContains) + } + return + } + require.NoError(err) + verifyResults := auth.Verify(retCtx.(context.Context)) + assert.NotEmpty(verifyResults) + }) + } +} + +func Test_errorInterceptor(t *testing.T) { + ctx := context.Background() + tests := []struct { + name string + factoryCtx context.Context + wantRequestErr bool + wantDomainErr bool + wantApiErr bool + }{ + { + name: "domain-error", + factoryCtx: ctx, + wantRequestErr: true, + wantDomainErr: true, + }, + { + name: "api-error", + factoryCtx: ctx, + wantRequestErr: true, + wantApiErr: true, + }, + { + name: "success", + factoryCtx: ctx, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + require.Falsef(tt.wantApiErr && tt.wantDomainErr, "tests can't require both wantApiErr and wantDomainErr") + errInterceptor := errorInterceptor(tt.factoryCtx) + client := startTestGreeterService(t, &testGreeter{}, errInterceptor) + var header metadata.MD + resp, err := client.SayHello( + context.Background(), + &interceptor.SayHelloRequest{Name: tt.name}, + grpc.Header(&header), + ) + if tt.wantRequestErr { + require.Error(err) + domainHdr := header.Get(domainErrHeader) + apiErrHdr := header.Get(apiErrHeader) + require.Error(err) + assert.Nil(resp) + if tt.wantDomainErr { + require.Len(domainHdr, 1) + assert.Len(apiErrHdr, 0) + decoded, err := base58.FastBase58Decoding(domainHdr[0]) + require.NoError(err) + var pbErr pberrors.Err + err = proto.Unmarshal(decoded, &pbErr) + require.NoError(err) + } + if tt.wantApiErr { + require.Len(apiErrHdr, 1) + assert.Len(domainHdr, 0) + decoded, err := base58.FastBase58Decoding(apiErrHdr[0]) + require.NoError(err) + var pbErr pberrors.ApiError + err = proto.Unmarshal(decoded, &pbErr) + require.NoError(err) + } + return + } + require.NoError(err) + }) + } +} + +func Test_statusCodeInterceptor(t *testing.T) { + ctx := context.Background() + tests := []struct { + name string + wantStatusCode int + wantErr bool + }{ + { + name: "nil-nil", + wantStatusCode: http.StatusNoContent, + }, + { + name: "nil-err", + wantErr: true, + }, + { + name: "hello", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + statusInterceptor := statusCodeInterceptor(ctx) + client := startTestGreeterService(t, &testGreeter{}, statusInterceptor) + var header metadata.MD + _, err := client.SayHello( + context.Background(), + &interceptor.SayHelloRequest{Name: tt.name}, + grpc.Header(&header), + ) + + if tt.wantErr { + assert.Error(err) + } + statusHdr := header.Get(handlers.StatusCodeHeader) + if tt.wantStatusCode > 0 { + require.Len(statusHdr, 1) + code, err := strconv.Atoi(statusHdr[0]) + require.NoError(err) + assert.Equal(tt.wantStatusCode, code) + } else { + require.Len(statusHdr, 0) + } + }) + } +} + +type testGreeter struct { + interceptor.UnimplementedGreeterServiceServer +} + +func (g *testGreeter) SayHello(ctx context.Context, req *interceptor.SayHelloRequest) (*interceptor.SayHelloResponse, error) { + const op = "SayHello" + switch req.GetName() { + case "api-error": + return &interceptor.SayHelloResponse{}, &handlers.ApiError{ + Status: http.StatusInternalServerError, + Inner: &pb_api.Error{ + Kind: "internal", + Op: op, + Message: "api error msg", + }, + } + case "domain-error": + return &interceptor.SayHelloResponse{Message: "hello"}, errors.New(ctx, errors.Internal, op, "domain error msg") + case "nil-nil": + return nil, nil + case "nil-err": + return nil, errors.New(ctx, errors.Internal, op, "nil response error msg") + default: + return &interceptor.SayHelloResponse{Message: "hello"}, nil + } +} diff --git a/internal/servers/controller/listeners.go b/internal/servers/controller/listeners.go index 9a0d2feae..ad62e2822 100644 --- a/internal/servers/controller/listeners.go +++ b/internal/servers/controller/listeners.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/cmd/base" pbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services" "github.com/hashicorp/boundary/internal/libs/alpnmux" @@ -20,10 +21,16 @@ import ( "google.golang.org/grpc" ) -func (c *Controller) startListeners() error { +func (c *Controller) startListeners(ctx context.Context) error { servers := make([]func(), 0, len(c.conf.Listeners)) configureForAPI := func(ln *base.ServerListener) error { + var err error + if c.gatewayServer, c.gatewayTicket, err = newGatewayServer(ctx, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms); err != nil { + return err + } + c.gatewayMux = newGatewayMux() + handler, err := c.handler(HandlerProperties{ ListenerConfig: ln.Config, CancelCtx: c.baseContext, @@ -134,6 +141,11 @@ func (c *Controller) startListeners() error { return nil } + c.gatewayListener, _ = newGatewayListener() + servers = append(servers, func() { + go c.gatewayServer.Serve(c.gatewayListener) + }) + for _, ln := range c.conf.Listeners { var err error for _, purpose := range ln.Config.Purpose { @@ -184,6 +196,21 @@ func (c *Controller) stopListeners(serversOnly bool) error { } }() } + + if c.gatewayServer != nil { + serverWg.Add(1) + go func() { + defer serverWg.Done() + shutdownKill, shutdownKillCancel := context.WithTimeout(c.baseContext, globals.DefaultMaxRequestDuration) + defer shutdownKillCancel() + go func() { + <-shutdownKill.Done() + c.gatewayServer.Stop() + }() + c.gatewayServer.GracefulStop() + }() + } + serverWg.Wait() if serversOnly { return nil diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index 247c6bc97..11691ad98 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -10,6 +10,7 @@ import ( "testing" "time" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/authmethods" "github.com/hashicorp/boundary/api/authtokens" @@ -18,6 +19,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/schema" + "github.com/hashicorp/boundary/internal/gen/testing/interceptor" "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/intglobals" @@ -28,6 +30,9 @@ import ( wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-secure-stdlib/base62" "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/test/bufconn" ) const ( @@ -718,3 +723,39 @@ func (tc *TestController) WaitForNextWorkerStatusUpdate(workerId string) error { event.WriteSysEvent(ctx, op, "waiting for next status report from worker received successfully", "worker", workerId) return nil } + +// startTestGreeterService is intended to facilitate the testing of +// interceptors. You provide a greeter service that produces appropriate +// responses to test your interceptor(s). This test function will start up a +// test greeter service wrapped with the specified interceptors and return an +// initialized client for the service. The test service will be stopped/cleaned +// up after the test (or subtests) have completed. +func startTestGreeterService(t *testing.T, greeter interceptor.GreeterServiceServer, interceptors ...grpc.UnaryServerInterceptor) interceptor.GreeterServiceClient { + t.Helper() + require := require.New(t) + dialCtx := context.Background() + + buffer := 1024 * 1024 + listener := bufconn.Listen(buffer) + + s := grpc.NewServer( + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(interceptors...)), + ) + interceptor.RegisterGreeterServiceServer(s, greeter) + go func() { + err := s.Serve(listener) + require.NoError(err) + }() + + conn, _ := grpc.DialContext(dialCtx, "", grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + }), grpc.WithInsecure()) + + t.Cleanup(func() { + listener.Close() + s.Stop() + }) + + client := interceptor.NewGreeterServiceClient(conn) + return client +} diff --git a/internal/servers/query.go b/internal/servers/query.go index 1e814a853..c0f865a17 100644 --- a/internal/servers/query.go +++ b/internal/servers/query.go @@ -1,18 +1,6 @@ package servers const ( - serverUpsertQuery = ` - insert into server - (private_id, type, description, address, update_time) - values - (@private_id, @type, @description, @address, now()) - on conflict on constraint server_pkey - do update set - type = @type, - description = @description, - address = @address, - update_time = now(); - ` deleteWhereCreateTimeSql = `create_time < ?` deleteTagsSql = `server_id = ?` ) diff --git a/internal/servers/repository.go b/internal/servers/repository.go index 2634e87a7..1a5a49639 100644 --- a/internal/servers/repository.go +++ b/internal/servers/repository.go @@ -2,7 +2,6 @@ package servers import ( "context" - "database/sql" "fmt" "time" @@ -138,7 +137,7 @@ func (r *Repository) UpsertServer(ctx context.Context, server *Server, opt ...Op opts := getOpts(opt...) - var rowsUpdated int + var rowsUpdated int64 var controllers []*Server _, err := r.writer.DoTx( ctx, @@ -146,14 +145,11 @@ func (r *Repository) UpsertServer(ctx context.Context, server *Server, opt ...Op db.ExpBackoff{}, func(read db.Reader, w db.Writer) error { var err error - rowsUpdated, err = w.Exec(ctx, - serverUpsertQuery, - []interface{}{ - sql.Named("private_id", server.PrivateId), - sql.Named("type", server.Type), - sql.Named("description", server.Description), - sql.Named("address", server.Address), - }) + onConflict := &db.OnConflict{ + Target: db.Constraint("server_pkey"), + Action: append(db.SetColumns([]string{"type", "description", "address"}), db.SetColumnValues(map[string]interface{}{"update_time": "now()"})...), + } + err = w.Create(ctx, server, db.WithOnConflict(onConflict), db.WithReturnRowsAffected(&rowsUpdated)) if err != nil { return errors.Wrap(ctx, err, op+":Upsert") } @@ -208,7 +204,7 @@ func (r *Repository) UpsertServer(ctx context.Context, server *Server, opt ...Op return nil, db.NoRowsAffected, err } - return controllers, rowsUpdated, nil + return controllers, int(rowsUpdated), nil } type RecoveryNonce struct { diff --git a/internal/servers/servers.pb.go b/internal/servers/servers.pb.go index b5fad0c29..cd07d8563 100644 --- a/internal/servers/servers.pb.go +++ b/internal/servers/servers.pb.go @@ -35,9 +35,10 @@ type Server struct { // private ID and name to be identical, and there is currently no reason we // can think of to allow a given server's name to match another within the // same cluster. So we simply use the private ID and deprecate this. + // @inject_tag: `gorm:"-"` // // Deprecated: Do not use. - Name string `protobuf:"bytes,30,opt,name=name,proto3" json:"name,omitempty"` + Name string `protobuf:"bytes,30,opt,name=name,proto3" json:"name,omitempty" gorm:"-"` // Description of the resource Description string `protobuf:"bytes,40,opt,name=description,proto3" json:"description,omitempty"` // Address for the server diff --git a/internal/session/repository_session_test.go b/internal/session/repository_session_test.go index 890e5b260..68e9076f1 100644 --- a/internal/session/repository_session_test.go +++ b/internal/session/repository_session_test.go @@ -15,7 +15,8 @@ import ( "github.com/hashicorp/boundary/internal/host/static" staticStore "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/target" - targetStore "github.com/hashicorp/boundary/internal/target/store" + "github.com/hashicorp/boundary/internal/target/tcp" + tcpStore "github.com/hashicorp/boundary/internal/target/tcp/store" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/jackc/pgconn" "google.golang.org/protobuf/types/known/timestamppb" @@ -1314,8 +1315,8 @@ func TestRepository_CancelSessionViaFKNull(t *testing.T) { cancelFk: func() cancelFk { s := setupFn() - t := &target.TcpTarget{ - TcpTarget: &targetStore.TcpTarget{ + t := &tcp.Target{ + Target: &tcpStore.Target{ PublicId: s.TargetId, }, } diff --git a/internal/session/testing.go b/internal/session/testing.go index 652f5ac98..7a96e2778 100644 --- a/internal/session/testing.go +++ b/internal/session/testing.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-secure-stdlib/base62" "github.com/hashicorp/go-uuid" @@ -132,7 +133,7 @@ func TestSessionParams(t *testing.T, conn *db.DB, wrapper wrapping.Wrapper, iamR sets := static.TestSets(t, conn, cats[0].PublicId, 1) _ = static.TestSetMembers(t, conn, sets[0].PublicId, hosts) - tcpTarget := target.TestTcpTarget(t, conn, proj.PublicId, "test target") + tcpTarget := tcp.TestTarget(t, conn, proj.PublicId, "test target") kms := kms.TestKms(t, conn, wrapper) targetRepo, err := target.NewRepository(rw, rw, kms) diff --git a/internal/target/credential_library_test.go b/internal/target/credential_library_test.go index 38a86a4b9..a079f471b 100644 --- a/internal/target/credential_library_test.go +++ b/internal/target/credential_library_test.go @@ -1,10 +1,11 @@ -package target +package target_test import ( "testing" "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/target/store" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,7 +19,7 @@ func TestCredentialLibrary_New(t *testing.T) { tests := []struct { name string args args - want *CredentialLibrary + want *target.CredentialLibrary wantErr errors.Code }{ { @@ -41,7 +42,7 @@ func TestCredentialLibrary_New(t *testing.T) { targetId: "targ_0000000", libraryId: "lib_0000000", }, - want: &CredentialLibrary{ + want: &target.CredentialLibrary{ CredentialLibrary: &store.CredentialLibrary{ TargetId: "targ_0000000", CredentialLibraryId: "lib_0000000", @@ -54,7 +55,7 @@ func TestCredentialLibrary_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := NewCredentialLibrary(tt.args.targetId, tt.args.libraryId) + got, err := target.NewCredentialLibrary(tt.args.targetId, tt.args.libraryId) if tt.wantErr != 0 { assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) assert.Nil(got) diff --git a/internal/target/exports_test.go b/internal/target/exports_test.go new file mode 100644 index 000000000..cada8aa31 --- /dev/null +++ b/internal/target/exports_test.go @@ -0,0 +1,7 @@ +package target + +// Expose functions for use in tests. +var ( + AllocTargetView = allocTargetView + TargetsViewDefaultTable = targetsViewDefaultTable +) diff --git a/internal/target/host_set.go b/internal/target/host_set.go index 7ccee9ed5..8326dadda 100644 --- a/internal/target/host_set.go +++ b/internal/target/host_set.go @@ -44,13 +44,6 @@ func NewTargetHostSet(targetId, hostSetId string, _ ...Option) (*TargetHostSet, return t, nil } -// allocTargetHostSet will allocate a target host set -func allocTargetHostSet() TargetHostSet { - return TargetHostSet{ - TargetHostSet: &store.TargetHostSet{}, - } -} - // Clone creates a clone of the target host set func (t *TargetHostSet) Clone() interface{} { cp := proto.Clone(t.TargetHostSet) diff --git a/internal/target/ids.go b/internal/target/ids.go deleted file mode 100644 index 618572621..000000000 --- a/internal/target/ids.go +++ /dev/null @@ -1,19 +0,0 @@ -package target - -import ( - "github.com/hashicorp/boundary/internal/db" - "github.com/hashicorp/boundary/internal/errors" -) - -const ( - TcpTargetPrefix = "ttcp" -) - -func newTcpTargetId() (string, error) { - const op = "target.newTcpTargetId" - id, err := db.NewPublicId(TcpTargetPrefix) - if err != nil { - return "", errors.WrapDeprecated(err, op) - } - return id, nil -} diff --git a/internal/target/ids_test.go b/internal/target/ids_test.go deleted file mode 100644 index eac126b1a..000000000 --- a/internal/target/ids_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package target - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_Ids(t *testing.T) { - t.Parallel() - t.Run("tcp", func(t *testing.T) { - id, err := newTcpTargetId() - require.NoError(t, err) - assert.True(t, strings.HasPrefix(id, TcpTargetPrefix+"_")) - }) -} diff --git a/internal/target/options.go b/internal/target/options.go index 1f39fb47b..5fa1bbe08 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -1,9 +1,13 @@ package target -import "time" +import ( + "time" -// getOpts - iterate the inbound Options and return a struct -func getOpts(opt ...Option) options { + "github.com/hashicorp/boundary/internal/types/subtypes" +) + +// GetOpts - iterate the inbound Options and return a struct +func GetOpts(opt ...Option) options { opts := getDefaultOptions() for _, o := range opt { o(&opts) @@ -16,54 +20,54 @@ type Option func(*options) // options = how options are represented type options struct { - withName string - withDescription string - withDefaultPort uint32 - withLimit int - withScopeId string - withScopeIds []string - withScopeName string - withUserId string - withTargetType *TargetType - withHostSources []string - withCredentialSources []string - withSessionMaxSeconds uint32 - withSessionConnectionLimit int32 - withPublicId string - withWorkerFilter string + WithName string + WithDescription string + WithDefaultPort uint32 + WithLimit int + WithScopeId string + WithScopeIds []string + WithScopeName string + WithUserId string + WithType subtypes.Subtype + WithHostSources []string + WithCredentialSources []string + WithSessionMaxSeconds uint32 + WithSessionConnectionLimit int32 + WithPublicId string + WithWorkerFilter string } func getDefaultOptions() options { return options{ - withName: "", - withDescription: "", - withLimit: 0, - withDefaultPort: 0, - withScopeId: "", - withScopeIds: nil, - withScopeName: "", - withUserId: "", - withTargetType: nil, - withHostSources: nil, - withCredentialSources: nil, - withSessionMaxSeconds: uint32((8 * time.Hour).Seconds()), - withSessionConnectionLimit: 1, - withPublicId: "", - withWorkerFilter: "", + WithName: "", + WithDescription: "", + WithLimit: 0, + WithDefaultPort: 0, + WithScopeId: "", + WithScopeIds: nil, + WithScopeName: "", + WithUserId: "", + WithType: "", + WithHostSources: nil, + WithCredentialSources: nil, + WithSessionMaxSeconds: uint32((8 * time.Hour).Seconds()), + WithSessionConnectionLimit: 1, + WithPublicId: "", + WithWorkerFilter: "", } } // WithDescription provides an optional description func WithDescription(desc string) Option { return func(o *options) { - o.withDescription = desc + o.WithDescription = desc } } // WithName provides an option to search by a friendly name func WithName(name string) Option { return func(o *options) { - o.withName = name + o.WithName = name } } @@ -72,88 +76,88 @@ func WithName(name string) Option { // If WithLimit == 0, then default limits are used for results. func WithLimit(limit int) Option { return func(o *options) { - o.withLimit = limit + o.WithLimit = limit } } // WithDefaultPort provides an option to specify the default target port. func WithDefaultPort(p uint32) Option { return func(o *options) { - o.withDefaultPort = p + o.WithDefaultPort = p } } // WithScopeId provides an option to search by a scope id func WithScopeId(scopeId string) Option { return func(o *options) { - o.withScopeId = scopeId + o.WithScopeId = scopeId } } // WithScopeId provides an option to search by multiple scope id func WithScopeIds(scopeIds []string) Option { return func(o *options) { - o.withScopeIds = scopeIds + o.WithScopeIds = scopeIds } } // WithScopeId provides an option to search by a scope name func WithScopeName(scopeName string) Option { return func(o *options) { - o.withScopeName = scopeName + o.WithScopeName = scopeName } } // WithUserId provides an option to search by a user public id func WithUserId(userId string) Option { return func(o *options) { - o.withUserId = userId + o.WithUserId = userId } } -// WithTargetType provides an option to search by a target type -func WithTargetType(t TargetType) Option { +// WithType provides an option to search by a target type +func WithType(t subtypes.Subtype) Option { return func(o *options) { - o.withTargetType = &t + o.WithType = t } } // WithHostSources provides an option for providing a list of host source ids func WithHostSources(hs []string) Option { return func(o *options) { - o.withHostSources = hs + o.WithHostSources = hs } } // WithCredentialSources provides an option for providing a list of credential source ids func WithCredentialSources(cl []string) Option { return func(o *options) { - o.withCredentialSources = cl + o.WithCredentialSources = cl } } func WithSessionMaxSeconds(dur uint32) Option { return func(o *options) { - o.withSessionMaxSeconds = dur + o.WithSessionMaxSeconds = dur } } func WithSessionConnectionLimit(limit int32) Option { return func(o *options) { - o.withSessionConnectionLimit = limit + o.WithSessionConnectionLimit = limit } } // WithPublicId provides an optional public id func WithPublicId(id string) Option { return func(o *options) { - o.withPublicId = id + o.WithPublicId = id } } // WithWorkerFilter provides an optional worker filter func WithWorkerFilter(filter string) Option { return func(o *options) { - o.withWorkerFilter = filter + o.WithWorkerFilter = filter } } diff --git a/internal/target/options_test.go b/internal/target/options_test.go index 298cb373c..a4d3431f5 100644 --- a/internal/target/options_test.go +++ b/internal/target/options_test.go @@ -3,6 +3,7 @@ package target import ( "testing" + "github.com/hashicorp/boundary/internal/types/subtypes" "github.com/stretchr/testify/assert" ) @@ -11,104 +12,105 @@ func Test_GetOpts(t *testing.T) { t.Parallel() t.Run("WithName", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithName("test")) + opts := GetOpts(WithName("test")) testOpts := getDefaultOptions() - testOpts.withName = "test" + testOpts.WithName = "test" assert.Equal(opts, testOpts) }) t.Run("WithDescription", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithDescription("test desc")) + opts := GetOpts(WithDescription("test desc")) testOpts := getDefaultOptions() - testOpts.withDescription = "test desc" + testOpts.WithDescription = "test desc" assert.Equal(opts, testOpts) }) t.Run("WithLimit", func(t *testing.T) { assert := assert.New(t) // test default of 0 - opts := getOpts() + opts := GetOpts() testOpts := getDefaultOptions() - testOpts.withLimit = 0 + testOpts.WithLimit = 0 assert.Equal(opts, testOpts) - opts = getOpts(WithLimit(-1)) + opts = GetOpts(WithLimit(-1)) testOpts = getDefaultOptions() - testOpts.withLimit = -1 + testOpts.WithLimit = -1 assert.Equal(opts, testOpts) - opts = getOpts(WithLimit(1)) + opts = GetOpts(WithLimit(1)) testOpts = getDefaultOptions() - testOpts.withLimit = 1 + testOpts.WithLimit = 1 assert.Equal(opts, testOpts) }) t.Run("WithDefaultPort", func(t *testing.T) { assert := assert.New(t) // test default of 0 - opts := getOpts() + opts := GetOpts() testOpts := getDefaultOptions() - testOpts.withDefaultPort = 0 + testOpts.WithDefaultPort = 0 assert.Equal(opts, testOpts) - opts = getOpts(WithDefaultPort(22)) + opts = GetOpts(WithDefaultPort(22)) testOpts = getDefaultOptions() - testOpts.withDefaultPort = uint32(22) + testOpts.WithDefaultPort = uint32(22) assert.Equal(opts, testOpts) }) t.Run("WithUserId", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithUserId("testId")) + opts := GetOpts(WithUserId("testId")) testOpts := getDefaultOptions() - testOpts.withUserId = "testId" + testOpts.WithUserId = "testId" assert.Equal(opts, testOpts) }) t.Run("WithScopeId", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithScopeId("testId")) + opts := GetOpts(WithScopeId("testId")) testOpts := getDefaultOptions() - testOpts.withScopeId = "testId" + testOpts.WithScopeId = "testId" assert.Equal(opts, testOpts) }) t.Run("WithScopeName", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithScopeName("testName")) + opts := GetOpts(WithScopeName("testName")) testOpts := getDefaultOptions() - testOpts.withScopeName = "testName" + testOpts.WithScopeName = "testName" assert.Equal(opts, testOpts) }) t.Run("WithPublicId", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithPublicId("testId")) + opts := GetOpts(WithPublicId("testId")) testOpts := getDefaultOptions() - testOpts.withPublicId = "testId" + testOpts.WithPublicId = "testId" assert.Equal(opts, testOpts) }) - t.Run("WithTargetType", func(t *testing.T) { + t.Run("WithType", func(t *testing.T) { + subtype := subtypes.Subtype("testtype") assert := assert.New(t) - opts := getOpts(WithTargetType(TcpTargetType)) + opts := GetOpts(WithType(subtype)) testOpts := getDefaultOptions() - target := TcpTargetType - testOpts.withTargetType = &target + target := subtype + testOpts.WithType = target assert.Equal(opts, testOpts) }) t.Run("WithHostSources", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithHostSources([]string{"alice", "bob"})) + opts := GetOpts(WithHostSources([]string{"alice", "bob"})) testOpts := getDefaultOptions() - testOpts.withHostSources = []string{"alice", "bob"} + testOpts.WithHostSources = []string{"alice", "bob"} assert.Equal(opts, testOpts) }) t.Run("WithWorkerFilter", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithWorkerFilter(`"/foo" == "bar"`)) + opts := GetOpts(WithWorkerFilter(`"/foo" == "bar"`)) testOpts := getDefaultOptions() - testOpts.withWorkerFilter = `"/foo" == "bar"` + testOpts.WithWorkerFilter = `"/foo" == "bar"` assert.Equal(opts, testOpts) }) t.Run("WithCredentialSources", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithCredentialSources([]string{"alice", "bob"})) + opts := GetOpts(WithCredentialSources([]string{"alice", "bob"})) testOpts := getDefaultOptions() - testOpts.withCredentialSources = []string{"alice", "bob"} + testOpts.WithCredentialSources = []string{"alice", "bob"} assert.Equal(opts, testOpts) }) } diff --git a/internal/target/registry.go b/internal/target/registry.go new file mode 100644 index 000000000..d33f3035c --- /dev/null +++ b/internal/target/registry.go @@ -0,0 +1,112 @@ +package target + +import ( + "context" + "fmt" + "sync" + + "github.com/hashicorp/boundary/internal/types/subtypes" +) + +// AllocFunc is a function that creates an in-memory Target. +type AllocFunc func() Target + +// VetFunc is a function that checks the given Target to ensure it can +// be used by the Repository. +type VetFunc func(context.Context, Target) error + +type registryEntry struct { + alloc AllocFunc + vet VetFunc + prefix string +} + +type registry struct { + m map[subtypes.Subtype]*registryEntry + subtypes *subtypes.Registry + + sync.RWMutex +} + +func (r *registry) set(s subtypes.Subtype, entry *registryEntry) { + r.Lock() + defer r.Unlock() + + _, previouslySet := r.m[s] + if previouslySet { + panic(fmt.Sprintf("target subtype %s already registered", s)) + } + + if err := r.subtypes.Register(s, entry.prefix); err != nil { + panic(err) + } + + r.m[s] = entry +} + +func (r *registry) get(s subtypes.Subtype) (*registryEntry, bool) { + r.RLock() + defer r.RUnlock() + + entry, ok := r.m[s] + if ok { + return entry, ok + } + return nil, ok +} + +func (r *registry) allocFunc(s subtypes.Subtype) (AllocFunc, bool) { + entry, ok := r.get(s) + if !ok { + return nil, ok + } + return entry.alloc, ok +} + +func (r *registry) vetFunc(s subtypes.Subtype) (VetFunc, bool) { + entry, ok := r.get(s) + if !ok { + return nil, ok + } + return entry.vet, ok +} + +func (r *registry) idPrefix(s subtypes.Subtype) (string, bool) { + entry, ok := r.get(s) + if !ok { + return "", ok + } + return entry.prefix, ok +} + +var subtypeRegistry = registry{ + m: make(map[subtypes.Subtype]*registryEntry), + subtypes: subtypes.NewRegistry(), +} + +// SubtypeFromType returns the Subtype from the provided string or if +// no Subtype was registered with that string Unknown is returned. +func SubtypeFromType(t string) subtypes.Subtype { + subtypeRegistry.RLock() + defer subtypeRegistry.RUnlock() + return subtypeRegistry.subtypes.SubtypeFromType(t) +} + +// SubtypeFromId returns the Subtype from the provided id if the id's prefix +// was registered with a Subtype. Otherwise Unknown is returned. +func SubtypeFromId(id string) subtypes.Subtype { + subtypeRegistry.RLock() + defer subtypeRegistry.RUnlock() + return subtypeRegistry.subtypes.SubtypeFromId(id) +} + +// Register registers repository hooks and the prefixes for a provided Subtype. Register +// panics if the subtype has already been registered or if any of the +// prefixes are associated with another subtype. +func Register(s subtypes.Subtype, af AllocFunc, vf VetFunc, prefix string) { + subtypeRegistry.set(s, ®istryEntry{ + alloc: af, + vet: vf, + prefix: prefix, + }) +} diff --git a/internal/target/repository.go b/internal/target/repository.go index dff2cbebd..04843199c 100644 --- a/internal/target/repository.go +++ b/internal/target/repository.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/hashicorp/boundary/internal/db" + dbcommon "github.com/hashicorp/boundary/internal/db/common" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" @@ -13,7 +14,7 @@ import ( // Cloneable provides a cloning interface type Cloneable interface { - Clone() interface{} + Clone() Target } // Repository is the target database repository @@ -39,16 +40,16 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo if kms == nil { return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil kms") } - opts := getOpts(opt...) - if opts.withLimit == 0 { + opts := GetOpts(opt...) + if opts.WithLimit == 0 { // zero signals the boundary defaults should be used. - opts.withLimit = db.DefaultLimit + opts.WithLimit = db.DefaultLimit } return &Repository{ reader: r, writer: w, kms: kms, - defaultLimit: opts.withLimit, + defaultLimit: opts.WithLimit, }, nil } @@ -57,7 +58,7 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo // found, it will return nil, nil, nil, nil. No options are currently supported. func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, opt ...Option) (Target, []HostSource, []CredentialSource, error) { const op = "target.(Repository).LookupTarget" - opts := getOpts(opt...) + opts := GetOpts(opt...) if publicIdOrName == "" { return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id") @@ -65,23 +66,23 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op var where []string var whereArgs []interface{} - nameEmpty := opts.withName == "" - scopeIdEmpty := opts.withScopeId == "" - scopeNameEmpty := opts.withScopeName == "" + nameEmpty := opts.WithName == "" + scopeIdEmpty := opts.WithScopeId == "" + scopeNameEmpty := opts.WithScopeName == "" if !nameEmpty { - if opts.withName != publicIdOrName { + if opts.WithName != publicIdOrName { return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "name passed in but does not match publicId") } - where, whereArgs = append(where, "lower(name) = lower(?)"), append(whereArgs, opts.withName) + where, whereArgs = append(where, "lower(name) = lower(?)"), append(whereArgs, opts.WithName) switch { case scopeIdEmpty && scopeNameEmpty: return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "using name but both scope ID and scope name are empty") case !scopeIdEmpty && !scopeNameEmpty: return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "using name but both scope ID and scope name are set") case !scopeIdEmpty: - where, whereArgs = append(where, "scope_id = ?"), append(whereArgs, opts.withScopeId) + where, whereArgs = append(where, "scope_id = ?"), append(whereArgs, opts.WithScopeId) case !scopeNameEmpty: - where, whereArgs = append(where, "scope_id = (select public_id from iam_scope where lower(name) = lower(?))"), append(whereArgs, opts.withScopeName) + where, whereArgs = append(where, "scope_id = (select public_id from iam_scope where lower(name) = lower(?))"), append(whereArgs, opts.WithScopeName) default: return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "unknown combination of parameters") } @@ -130,28 +131,28 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op } return nil, nil, nil, errors.Wrap(ctx, err, op) } - subtype, err := target.targetSubtype() + subtype, err := target.targetSubtype(ctx) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op) } return subtype, hostSources, credSources, nil } -// ListTargets in targets in a scope. Supports the WithScopeId, WithLimit, WithTargetType options. +// ListTargets in targets in a scope. Supports the WithScopeId, WithLimit, WithType options. func (r *Repository) ListTargets(ctx context.Context, opt ...Option) ([]Target, error) { const op = "target.(Repository).ListTargets" - opts := getOpts(opt...) - if len(opts.withScopeIds) == 0 && opts.withUserId == "" { + opts := GetOpts(opt...) + if len(opts.WithScopeIds) == 0 && opts.WithUserId == "" { return nil, errors.New(ctx, errors.InvalidParameter, op, "must specify either scope id or user id") } // TODO (jimlambrt 8/2020) - implement WithUserId() optional filtering. var where []string var args []interface{} - if len(opts.withScopeIds) != 0 { - where, args = append(where, "scope_id in (?)"), append(args, opts.withScopeIds) + if len(opts.WithScopeIds) != 0 { + where, args = append(where, "scope_id in (?)"), append(args, opts.WithScopeIds) } - if opts.withTargetType != nil { - where, args = append(where, "type = ?"), append(args, opts.withTargetType.String()) + if opts.WithType != "" { + where, args = append(where, "type = ?"), append(args, opts.WithType.String()) } var foundTargets []*targetView @@ -163,7 +164,7 @@ func (r *Repository) ListTargets(ctx context.Context, opt ...Option) ([]Target, targets := make([]Target, 0, len(foundTargets)) for _, t := range foundTargets { - subtype, err := t.targetSubtype() + subtype, err := t.targetSubtype(ctx) if err != nil { return nil, errors.Wrap(ctx, err, op) } @@ -176,12 +177,12 @@ func (r *Repository) ListTargets(ctx context.Context, opt ...Option) ([]Target, // repo defaultLimit func (r *Repository) list(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error { const op = "target.(Repository).list" - opts := getOpts(opt...) + opts := GetOpts(opt...) limit := r.defaultLimit var dbOpts []db.Option - if opts.withLimit != 0 { + if opts.WithLimit != 0 { // non-zero signals an override of the default limit for the repo. - limit = opts.withLimit + limit = opts.WithLimit } dbOpts = append(dbOpts, db.WithLimit(limit)) if err := r.reader.SearchWhere(ctx, resources, where, args, dbOpts...); err != nil { @@ -202,17 +203,16 @@ func (r *Repository) DeleteTarget(ctx context.Context, publicId string, _ ...Opt return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId))) } var metadata oplog.Metadata - var deleteTarget interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = publicId - deleteTarget = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_DELETE) - default: + var deleteTarget Target + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", publicId, t.Type)) } + deleteTarget = alloc() + deleteTarget.SetPublicId(ctx, publicId) + metadata = deleteTarget.Oplog(oplog.OpType_OP_TYPE_DELETE) + oplogWrapper, err := r.kms.GetWrapper(ctx, t.ScopeId, kms.KeyPurposeOplog) if err != nil { return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) @@ -277,7 +277,7 @@ func (r *Repository) update(ctx context.Context, target Target, version uint32, if err != nil { return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) } - metadata := target.oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata := target.Oplog(oplog.OpType_OP_TYPE_UPDATE) dbOpts = append(dbOpts, db.WithOplog(oplogWrapper, metadata)) var rowsUpdated int @@ -320,3 +320,202 @@ func (r *Repository) update(ctx context.Context, target Target, version uint32, } return returnedTarget.(Target), hostSources, credSources, rowsUpdated, nil } + +// CreateTarget inserts into the repository and returns the new Target with +// its list of host sets and credential libraries. +// WithHostSources and WithCredentialSources are the only supported option. +func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Option) (Target, []HostSource, []CredentialSource, error) { + const op = "target.(Repository).CreateTarget" + opts := GetOpts(opt...) + if target == nil { + return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing target") + } + + vet, ok := subtypeRegistry.vetFunc(target.GetType()) + if !ok { + return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", target.GetType())) + } + if err := vet(ctx, target); err != nil { + return nil, nil, nil, err + } + if target.GetScopeId() == "" { + return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + if target.GetName() == "" { + return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing name") + } + if target.GetPublicId() != "" { + return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + } + + t := target.Clone() + + if opts.WithPublicId != "" { + if err := t.SetPublicId(ctx, opts.WithPublicId); err != nil { + return nil, nil, nil, err + } + } else { + prefix, ok := subtypeRegistry.idPrefix(target.GetType()) + if !ok { + return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", target.GetType())) + } + id, err := db.NewPublicId(prefix) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op) + } + t.SetPublicId(ctx, id) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, target.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + newHostSets := make([]interface{}, 0, len(opts.WithHostSources)) + for _, hsId := range opts.WithHostSources { + hostSet, err := NewTargetHostSet(t.GetPublicId(), hsId) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory target host set")) + } + newHostSets = append(newHostSets, hostSet) + } + + newCredLibs := make([]interface{}, 0, len(opts.WithCredentialSources)) + for _, clId := range opts.WithCredentialSources { + credLib, err := NewCredentialLibrary(t.GetPublicId(), clId) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory target credential library")) + } + newCredLibs = append(newCredLibs, credLib) + } + + metadata := t.Oplog(oplog.OpType_OP_TYPE_CREATE) + var returnedTarget interface{} + var returnedHostSources []HostSource + var returnedCredSources []CredentialSource + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(read db.Reader, w db.Writer) error { + targetTicket, err := w.GetTicket(t) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + msgs := make([]*oplog.Message, 0, 2) + var targetOplogMsg oplog.Message + returnedTarget = t.Clone() + if err := w.Create(ctx, returnedTarget, db.NewOplogMsg(&targetOplogMsg)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target")) + } + msgs = append(msgs, &targetOplogMsg) + if len(newHostSets) > 0 { + hostSetOplogMsgs := make([]*oplog.Message, 0, len(newHostSets)) + if err := w.CreateItems(ctx, newHostSets, db.NewOplogMsgs(&hostSetOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add host sets")) + } + if returnedHostSources, err = fetchHostSources(ctx, read, t.GetPublicId()); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to read host sources")) + } + msgs = append(msgs, hostSetOplogMsgs...) + } + if len(newCredLibs) > 0 { + credLibOplogMsgs := make([]*oplog.Message, 0, len(newCredLibs)) + if err := w.CreateItems(ctx, newCredLibs, db.NewOplogMsgs(&credLibOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add credential sources")) + } + if returnedCredSources, err = fetchCredentialSources(ctx, read, t.GetPublicId()); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to read credential sources")) + } + msgs = append(msgs, credLibOplogMsgs...) + } + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + + return nil + }, + ) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s target id", t.GetPublicId()))) + } + return returnedTarget.(Target), returnedHostSources, returnedCredSources, nil +} + +// UpdateTarget will update a target in the repository and return the written +// target. fieldMaskPaths provides field_mask.proto paths for fields that should +// be updated. Fields will be set to NULL if the field is a zero value and +// included in fieldMask. Name, Description, and WorkerFilter are the only +// updatable fields. If no updatable fields are included in the fieldMaskPaths, +// then an error is returned. +func (r *Repository) UpdateTarget(ctx context.Context, target Target, version uint32, fieldMaskPaths []string, _ ...Option) (Target, []HostSource, []CredentialSource, int, error) { + const op = "target.(Repository).UpdateTarget" + if target == nil { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target") + } + vet, ok := subtypeRegistry.vetFunc(target.GetType()) + if !ok { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", target.GetType())) + } + if err := vet(ctx, target); err != nil { + return nil, nil, nil, db.NoRowsAffected, err + } + + if target.GetPublicId() == "" { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target public id") + } + + for _, f := range fieldMaskPaths { + switch { + case strings.EqualFold("name", f): + case strings.EqualFold("description", f): + case strings.EqualFold("defaultport", f): + case strings.EqualFold("sessionmaxseconds", f): + case strings.EqualFold("sessionconnectionlimit", f): + case strings.EqualFold("workerfilter", f): + default: + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) + } + } + var dbMask, nullFields []string + dbMask, nullFields = dbcommon.BuildUpdatePaths( + map[string]interface{}{ + "Name": target.GetName(), + "Description": target.GetDescription(), + "DefaultPort": target.GetDefaultPort(), + "SessionMaxSeconds": target.GetSessionMaxSeconds(), + "SessionConnectionLimit": target.GetSessionConnectionLimit(), + "WorkerFilter": target.GetWorkerFilter(), + }, + fieldMaskPaths, + []string{"SessionMaxSeconds", "SessionConnectionLimit"}, + ) + if len(dbMask) == 0 && len(nullFields) == 0 { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "empty field mask") + } + var returnedTarget Target + var rowsUpdated int + var hostSources []HostSource + var credSources []CredentialSource + _, err := r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(read db.Reader, w db.Writer) error { + var err error + t := target.Clone() + returnedTarget, hostSources, credSources, rowsUpdated, err = r.update(ctx, t, version, dbMask, nullFields) + if err != nil { + return errors.Wrap(ctx, err, op) + } + return nil + }, + ) + if err != nil { + if errors.IsUniqueError(err) { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.NotUnique, op, fmt.Sprintf("target %s already exists in scope %s", target.GetName(), target.GetScopeId())) + } + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", target.GetPublicId()))) + } + return returnedTarget, hostSources, credSources, rowsUpdated, nil +} diff --git a/internal/target/repository_credential_source.go b/internal/target/repository_credential_source.go index 2ebb5449f..31284bf1e 100644 --- a/internal/target/repository_credential_source.go +++ b/internal/target/repository_credential_source.go @@ -43,19 +43,18 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) } var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) - default: + + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } + target := alloc() + target.SetPublicId(ctx, t.PublicId) + target.SetVersion(targetVersion + 1) + metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) @@ -143,18 +142,16 @@ func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) } var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) - default: + + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } + target := alloc() + target.SetPublicId(ctx, t.PublicId) + target.SetVersion(targetVersion + 1) + metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) if err != nil { @@ -257,17 +254,16 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) } var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - default: + + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } + target := alloc() + target.SetPublicId(ctx, t.PublicId) + target.SetVersion(targetVersion + 1) + metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) if err != nil { return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) diff --git a/internal/target/repository_host_source.go b/internal/target/repository_host_source.go index de02b7911..013097c76 100644 --- a/internal/target/repository_host_source.go +++ b/internal/target/repository_host_source.go @@ -40,18 +40,18 @@ func (r *Repository) AddTargetHostSources(ctx context.Context, targetId string, return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) } var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) - default: + + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } + + target := alloc() + target.SetPublicId(ctx, t.PublicId) + target.SetVersion(targetVersion + 1) + metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) @@ -137,18 +137,18 @@ func (r *Repository) DeleteTargetHostSources(ctx context.Context, targetId strin } var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) - default: + + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } + + target := alloc() + target.SetPublicId(ctx, t.PublicId) + target.SetVersion(targetVersion + 1) + metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) if err != nil { return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) @@ -260,17 +260,17 @@ func (r *Repository) SetTargetHostSources(ctx context.Context, targetId string, } var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - default: + + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } + + target := alloc() + target.SetPublicId(ctx, t.PublicId) + target.SetVersion(targetVersion + 1) + metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) if err != nil { return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) diff --git a/internal/target/repository_tcp_target.go b/internal/target/repository_tcp_target.go deleted file mode 100644 index 1f6fe63a2..000000000 --- a/internal/target/repository_tcp_target.go +++ /dev/null @@ -1,198 +0,0 @@ -package target - -import ( - "context" - "fmt" - "strings" - - "github.com/hashicorp/boundary/internal/db" - dbcommon "github.com/hashicorp/boundary/internal/db/common" - "github.com/hashicorp/boundary/internal/errors" - "github.com/hashicorp/boundary/internal/kms" - "github.com/hashicorp/boundary/internal/oplog" -) - -// CreateTcpTarget inserts into the repository and returns the new Target with -// its list of host sets and credential libraries. -// WithHostSources and WithCredentialSources are the only supported option. -func (r *Repository) CreateTcpTarget(ctx context.Context, target *TcpTarget, opt ...Option) (Target, []HostSource, []CredentialSource, error) { - const op = "target.(Repository).CreateTcpTarget" - opts := getOpts(opt...) - if target == nil { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing target") - } - if target.TcpTarget == nil { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing target store") - } - if target.ScopeId == "" { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") - } - if target.Name == "" { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing name") - } - if target.PublicId != "" { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") - } - - t := target.Clone().(*TcpTarget) - - if opts.withPublicId != "" { - if !strings.HasPrefix(opts.withPublicId, TcpTargetPrefix+"_") { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("passed-in public ID %q has wrong prefix, should be %q", opts.withPublicId, TcpTargetPrefix)) - } - t.PublicId = opts.withPublicId - } else { - id, err := newTcpTargetId() - if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op) - } - t.PublicId = id - } - - oplogWrapper, err := r.kms.GetWrapper(ctx, target.ScopeId, kms.KeyPurposeOplog) - if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) - } - - newHostSets := make([]interface{}, 0, len(opts.withHostSources)) - for _, hsId := range opts.withHostSources { - hostSet, err := NewTargetHostSet(t.PublicId, hsId) - if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory target host set")) - } - newHostSets = append(newHostSets, hostSet) - } - - newCredLibs := make([]interface{}, 0, len(opts.withCredentialSources)) - for _, clId := range opts.withCredentialSources { - credLib, err := NewCredentialLibrary(t.PublicId, clId) - if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory target credential library")) - } - newCredLibs = append(newCredLibs, credLib) - } - - metadata := t.oplog(oplog.OpType_OP_TYPE_CREATE) - var returnedTarget interface{} - var returnedHostSources []HostSource - var returnedCredSources []CredentialSource - _, err = r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(read db.Reader, w db.Writer) error { - targetTicket, err := w.GetTicket(t) - if err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) - } - msgs := make([]*oplog.Message, 0, 2) - var targetOplogMsg oplog.Message - returnedTarget = t.Clone() - if err := w.Create(ctx, returnedTarget, db.NewOplogMsg(&targetOplogMsg)); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target")) - } - msgs = append(msgs, &targetOplogMsg) - if len(newHostSets) > 0 { - hostSetOplogMsgs := make([]*oplog.Message, 0, len(newHostSets)) - if err := w.CreateItems(ctx, newHostSets, db.NewOplogMsgs(&hostSetOplogMsgs)); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add host sets")) - } - if returnedHostSources, err = fetchHostSources(ctx, read, t.PublicId); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to read host sources")) - } - msgs = append(msgs, hostSetOplogMsgs...) - } - if len(newCredLibs) > 0 { - credLibOplogMsgs := make([]*oplog.Message, 0, len(newCredLibs)) - if err := w.CreateItems(ctx, newCredLibs, db.NewOplogMsgs(&credLibOplogMsgs)); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add credential sources")) - } - if returnedCredSources, err = fetchCredentialSources(ctx, read, t.PublicId); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to read credential sources")) - } - msgs = append(msgs, credLibOplogMsgs...) - } - if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) - } - - return nil - }, - ) - if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s target id", t.PublicId))) - } - return returnedTarget.(*TcpTarget), returnedHostSources, returnedCredSources, nil -} - -// UpdateTcpTarget will update a target in the repository and return the written -// target. fieldMaskPaths provides field_mask.proto paths for fields that should -// be updated. Fields will be set to NULL if the field is a zero value and -// included in fieldMask. Name, Description, and WorkerFilter are the only -// updatable fields. If no updatable fields are included in the fieldMaskPaths, -// then an error is returned. -func (r *Repository) UpdateTcpTarget(ctx context.Context, target *TcpTarget, version uint32, fieldMaskPaths []string, _ ...Option) (Target, []HostSource, []CredentialSource, int, error) { - const op = "target.(Repository).UpdateTcpTarget" - if target == nil { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target") - } - if target.TcpTarget == nil { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target store") - } - if target.PublicId == "" { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target public id") - } - for _, f := range fieldMaskPaths { - switch { - case strings.EqualFold("name", f): - case strings.EqualFold("description", f): - case strings.EqualFold("defaultport", f): - case strings.EqualFold("sessionmaxseconds", f): - case strings.EqualFold("sessionconnectionlimit", f): - case strings.EqualFold("workerfilter", f): - default: - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) - } - } - var dbMask, nullFields []string - dbMask, nullFields = dbcommon.BuildUpdatePaths( - map[string]interface{}{ - "Name": target.Name, - "Description": target.Description, - "DefaultPort": target.DefaultPort, - "SessionMaxSeconds": target.SessionMaxSeconds, - "SessionConnectionLimit": target.SessionConnectionLimit, - "WorkerFilter": target.WorkerFilter, - }, - fieldMaskPaths, - []string{"SessionMaxSeconds", "SessionConnectionLimit"}, - ) - if len(dbMask) == 0 && len(nullFields) == 0 { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "empty field mask") - } - var returnedTarget Target - var rowsUpdated int - var hostSources []HostSource - var credSources []CredentialSource - _, err := r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(read db.Reader, w db.Writer) error { - var err error - t := target.Clone().(*TcpTarget) - returnedTarget, hostSources, credSources, rowsUpdated, err = r.update(ctx, t, version, dbMask, nullFields) - if err != nil { - return errors.Wrap(ctx, err, op) - } - return nil - }, - ) - if err != nil { - if errors.IsUniqueError(err) { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.NotUnique, op, fmt.Sprintf("target %s already exists in scope %s", target.Name, target.ScopeId)) - } - return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", target.PublicId))) - } - return returnedTarget, hostSources, credSources, rowsUpdated, nil -} diff --git a/internal/target/repository_test.go b/internal/target/repository_test.go index 7530bd73a..6187421a3 100644 --- a/internal/target/repository_test.go +++ b/internal/target/repository_test.go @@ -1,17 +1,10 @@ package target import ( - "context" - "fmt" - "strconv" "testing" - "time" "github.com/hashicorp/boundary/internal/db" - "github.com/hashicorp/boundary/internal/errors" - "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" - "github.com/hashicorp/boundary/internal/oplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -93,336 +86,8 @@ func TestNewRepository(t *testing.T) { return } require.NoError(err) + assert.NotNil(got) assert.Equal(tt.want, got) }) } } - -func TestRepository_LookupTarget(t *testing.T) { - t.Parallel() - conn, _ := db.TestSetup(t, "postgres") - wrapper := db.TestWrapper(t) - testKms := kms.TestKms(t, conn, wrapper) - iamRepo := iam.TestRepo(t, conn, wrapper) - _, proj := iam.TestScopes(t, iamRepo) - proj.Name = "project-name" - _, _, err := iamRepo.UpdateScope(context.Background(), proj, 1, []string{"name"}) - require.NoError(t, err) - rw := db.New(conn) - repo, err := NewRepository(rw, rw, testKms) - require.NoError(t, err) - tgt := TestTcpTarget(t, conn, proj.PublicId, "target-name") - - tests := []struct { - testName string - id string - name string - scopeId string - scopeName string - wantErr bool - }{ - { - testName: "id", - id: tgt.PublicId, - wantErr: false, - }, - { - testName: "name only", - name: tgt.Name, - wantErr: true, - }, - { - testName: "scope id only", - scopeId: proj.PublicId, - wantErr: true, - }, - { - testName: "scope name only", - scopeName: proj.Name, - wantErr: true, - }, - { - testName: "scope name and id", - scopeId: proj.PublicId, - scopeName: proj.Name, - wantErr: true, - }, - { - testName: "everything", - name: tgt.Name, - scopeId: proj.PublicId, - scopeName: proj.Name, - wantErr: true, - }, - { - testName: "name and scope name", - name: tgt.Name, - scopeName: proj.Name, - wantErr: false, - }, - { - testName: "name and scope id", - name: tgt.Name, - scopeId: proj.PublicId, - wantErr: false, - }, - { - testName: "id and name", - id: tgt.PublicId, - name: tgt.Name, - scopeId: proj.PublicId, - wantErr: true, - }, - { - testName: "id and scope name", - id: tgt.PublicId, - scopeName: proj.Name, - wantErr: true, - }, - { - testName: "id and scope id", - id: tgt.PublicId, - scopeId: proj.PublicId, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.testName, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - id := tt.id - if tt.name != "" && tt.id == "" { - id = tt.name - } - var opts []Option - if tt.name != "" { - opts = append(opts, WithName(tt.name)) - } - if tt.scopeId != "" { - opts = append(opts, WithScopeId(tt.scopeId)) - } - if tt.scopeName != "" { - opts = append(opts, WithScopeName(tt.scopeName)) - } - got, _, _, err := repo.LookupTarget(context.Background(), id, opts...) - if tt.wantErr { - require.Error(err) - return - } - require.NoError(err) - assert.Equal(tgt.PublicId, got.GetPublicId()) - }) - } -} - -func TestRepository_ListTargets(t *testing.T) { - t.Parallel() - conn, _ := db.TestSetup(t, "postgres") - const testLimit = 10 - wrapper := db.TestWrapper(t) - testKms := kms.TestKms(t, conn, wrapper) - iamRepo := iam.TestRepo(t, conn, wrapper) - _, proj := iam.TestScopes(t, iamRepo) - rw := db.New(conn) - repo, err := NewRepository(rw, rw, testKms) - require.NoError(t, err) - repo.defaultLimit = testLimit - - type args struct { - opt []Option - } - tests := []struct { - name string - createCnt int - createScopeId string - createScopeId2 string - grantUserId string - args args - wantCnt int - wantErr bool - }{ - { - name: "tcp-target", - createCnt: 5, - createScopeId: proj.PublicId, - args: args{ - opt: []Option{WithTargetType(TcpTargetType), WithScopeIds([]string{proj.PublicId})}, - }, - wantCnt: 5, - wantErr: false, - }, - { - name: "no-limit", - createCnt: testLimit + 1, - createScopeId: proj.PublicId, - args: args{ - opt: []Option{WithLimit(-1), WithScopeIds([]string{proj.PublicId})}, - }, - wantCnt: testLimit + 1, - wantErr: false, - }, - { - name: "default-limit", - createCnt: testLimit + 1, - createScopeId: proj.PublicId, - args: args{ - opt: []Option{WithScopeIds([]string{proj.PublicId})}, - }, - wantCnt: testLimit, - wantErr: false, - }, - { - name: "custom-limit", - createCnt: testLimit + 1, - createScopeId: proj.PublicId, - args: args{ - opt: []Option{WithLimit(3), WithScopeIds([]string{proj.PublicId})}, - }, - wantCnt: 3, - wantErr: false, - }, - { - name: "bad-org", - createCnt: 1, - createScopeId: proj.PublicId, - args: args{ - opt: []Option{WithScopeIds([]string{"bad-id"})}, - }, - wantCnt: 0, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - require.NoError(conn.Where("1=1").Delete(allocTcpTarget()).Error) - testGroups := []*TcpTarget{} - for i := 0; i < tt.createCnt; i++ { - switch { - case tt.createScopeId2 != "" && i%2 == 0: - testGroups = append(testGroups, TestTcpTarget(t, conn, tt.createScopeId2, strconv.Itoa(i))) - default: - testGroups = append(testGroups, TestTcpTarget(t, conn, tt.createScopeId, strconv.Itoa(i))) - } - } - assert.Equal(tt.createCnt, len(testGroups)) - got, err := repo.ListTargets(context.Background(), tt.args.opt...) - if tt.wantErr { - require.Error(err) - return - } - require.NoError(err) - assert.Equal(tt.wantCnt, len(got)) - }) - } -} - -func TestRepository_ListRoles_Multiple_Scopes(t *testing.T) { - t.Parallel() - conn, _ := db.TestSetup(t, "postgres") - wrapper := db.TestWrapper(t) - testKms := kms.TestKms(t, conn, wrapper) - iamRepo := iam.TestRepo(t, conn, wrapper) - _, proj1 := iam.TestScopes(t, iamRepo) - _, proj2 := iam.TestScopes(t, iamRepo) - rw := db.New(conn) - repo, err := NewRepository(rw, rw, testKms) - require.NoError(t, err) - - require.NoError(t, conn.Where("1=1").Delete(allocTcpTarget()).Error) - - const numPerScope = 10 - var total int - for i := 0; i < numPerScope; i++ { - TestTcpTarget(t, conn, proj1.GetPublicId(), fmt.Sprintf("proj1-%d", i)) - total++ - TestTcpTarget(t, conn, proj2.GetPublicId(), fmt.Sprintf("proj2-%d", i)) - total++ - } - - got, err := repo.ListTargets(context.Background(), WithScopeIds([]string{"global", proj1.GetPublicId(), proj2.GetPublicId()})) - require.NoError(t, err) - assert.Equal(t, total, len(got)) -} - -func TestRepository_DeleteTarget(t *testing.T) { - t.Parallel() - conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) - wrapper := db.TestWrapper(t) - testKms := kms.TestKms(t, conn, wrapper) - iamRepo := iam.TestRepo(t, conn, wrapper) - _, proj := iam.TestScopes(t, iamRepo) - repo, err := NewRepository(rw, rw, testKms) - require.NoError(t, err) - - type args struct { - target Target - opt []Option - } - tests := []struct { - name string - args args - wantRowsDeleted int - wantErr bool - wantErrMsg string - }{ - { - name: "valid", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "valid"), - }, - wantRowsDeleted: 1, - wantErr: false, - }, - { - name: "no-public-id", - args: args{ - target: func() Target { - target := allocTcpTarget() - return &target - }(), - }, - wantRowsDeleted: 0, - wantErr: true, - wantErrMsg: "target.(Repository).DeleteTarget: missing public id: parameter violation: error #100", - }, - { - name: "not-found", - args: args{ - target: func() Target { - id, err := newTcpTargetId() - require.NoError(t, err) - target := allocTcpTarget() - target.PublicId = id - return &target - }(), - }, - wantRowsDeleted: 0, - wantErr: true, - wantErrMsg: "db.LookupById: record not found, search issue: error #1100", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - deletedRows, err := repo.DeleteTarget(context.Background(), tt.args.target.GetPublicId(), tt.args.opt...) - if tt.wantErr { - assert.Error(err) - assert.Equal(0, deletedRows) - assert.Contains(err.Error(), tt.wantErrMsg) - err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) - assert.Error(err) - assert.True(errors.IsNotFoundError(err)) - return - } - assert.NoError(err) - assert.Equal(tt.wantRowsDeleted, deletedRows) - foundGroup, _, _, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) - assert.NoError(err) - assert.Nil(foundGroup) - - err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) - assert.NoError(err) - }) - } -} diff --git a/internal/target/store/target.pb.go b/internal/target/store/target.pb.go index e248b8cd8..c3e8acdfd 100644 --- a/internal/target/store/target.pb.go +++ b/internal/target/store/target.pb.go @@ -253,157 +253,6 @@ func (x *TargetHostSet) GetCreateTime() *timestamp.Timestamp { return nil } -type TcpTarget struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // public_id is used to access the TargetTcp via an API - // @inject_tag: gorm:"primary_key" - PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` - // scope id for the TargetTcp - // @inject_tag: `gorm:"default:null"` - ScopeId string `protobuf:"bytes,20,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"` - // name is the optional friendly name used to - // access the TargetTcp via an API - // @inject_tag: `gorm:"default:null"` - Name string `protobuf:"bytes,30,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` - // description of the TargetTcp - // @inject_tag: `gorm:"default:null"` - Description string `protobuf:"bytes,40,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` - // create_time from the RDBMS - // @inject_tag: `gorm:"default:current_timestamp"` - CreateTime *timestamp.Timestamp `protobuf:"bytes,50,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` - // update_time from the RDBMS - // @inject_tag: `gorm:"default:current_timestamp"` - UpdateTime *timestamp.Timestamp `protobuf:"bytes,60,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` - // version allows optimistic locking of the TargetTcp when modifying the - // TargetTcp - // @inject_tag: `gorm:"default:null"` - Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` - // default port of the TargetTcp - // @inject_tag: `gorm:"default:null"` - DefaultPort uint32 `protobuf:"varint,80,opt,name=default_port,json=defaultPort,proto3" json:"default_port,omitempty" gorm:"default:null"` - // Maximum total lifetime of a created session, in seconds - // @inject_tag: `gorm:"default:null"` - SessionMaxSeconds uint32 `protobuf:"varint,100,opt,name=session_max_seconds,json=sessionMaxSeconds,proto3" json:"session_max_seconds,omitempty" gorm:"default:null"` - // Maximum number of connections in a session - // @inject_tag: `gorm:"default:null"` - SessionConnectionLimit int32 `protobuf:"varint,110,opt,name=session_connection_limit,json=sessionConnectionLimit,proto3" json:"session_connection_limit,omitempty" gorm:"default:null"` - // A boolean expression that allows filtering the workers that can handle a session - // @inject_tag: `gorm:"default:null"` - WorkerFilter string `protobuf:"bytes,120,opt,name=worker_filter,json=workerFilter,proto3" json:"worker_filter,omitempty" gorm:"default:null"` -} - -func (x *TcpTarget) Reset() { - *x = TcpTarget{} - if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TcpTarget) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TcpTarget) ProtoMessage() {} - -func (x *TcpTarget) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TcpTarget.ProtoReflect.Descriptor instead. -func (*TcpTarget) Descriptor() ([]byte, []int) { - return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{2} -} - -func (x *TcpTarget) GetPublicId() string { - if x != nil { - return x.PublicId - } - return "" -} - -func (x *TcpTarget) GetScopeId() string { - if x != nil { - return x.ScopeId - } - return "" -} - -func (x *TcpTarget) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *TcpTarget) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (x *TcpTarget) GetCreateTime() *timestamp.Timestamp { - if x != nil { - return x.CreateTime - } - return nil -} - -func (x *TcpTarget) GetUpdateTime() *timestamp.Timestamp { - if x != nil { - return x.UpdateTime - } - return nil -} - -func (x *TcpTarget) GetVersion() uint32 { - if x != nil { - return x.Version - } - return 0 -} - -func (x *TcpTarget) GetDefaultPort() uint32 { - if x != nil { - return x.DefaultPort - } - return 0 -} - -func (x *TcpTarget) GetSessionMaxSeconds() uint32 { - if x != nil { - return x.SessionMaxSeconds - } - return 0 -} - -func (x *TcpTarget) GetSessionConnectionLimit() int32 { - if x != nil { - return x.SessionConnectionLimit - } - return 0 -} - -func (x *TcpTarget) GetWorkerFilter() string { - if x != nil { - return x.WorkerFilter - } - return "" -} - type CredentialLibrary struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -426,7 +275,7 @@ type CredentialLibrary struct { func (x *CredentialLibrary) Reset() { *x = CredentialLibrary{} if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -439,7 +288,7 @@ func (x *CredentialLibrary) String() string { func (*CredentialLibrary) ProtoMessage() {} func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -452,7 +301,7 @@ func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialLibrary.ProtoReflect.Descriptor instead. func (*CredentialLibrary) Descriptor() ([]byte, []int) { - return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{3} + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{2} } func (x *CredentialLibrary) GetTargetId() string { @@ -538,70 +387,25 @@ var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ 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, 0xc6, 0x05, 0x0a, 0x09, 0x54, 0x63, 0x70, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 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, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x32, 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, - 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x3c, 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, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4d, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x2a, 0xc2, - 0xdd, 0x29, 0x26, 0x0a, 0x0b, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, - 0x12, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x5c, 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x64, 0x20, - 0x01, 0x28, 0x0d, 0x42, 0x2c, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x11, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x13, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x52, 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x70, 0x0a, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x6e, 0x20, 0x01, 0x28, 0x05, 0x42, 0x36, 0xc2, 0xdd, 0x29, 0x32, 0x0a, 0x16, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, - 0x69, 0x6d, 0x69, 0x74, 0x12, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x16, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x46, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x42, 0x21, 0xc2, - 0xdd, 0x29, 0x1d, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x12, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xe0, - 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, - 0x72, 0x61, 0x72, 0x79, 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, 0x32, 0x0a, 0x15, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, - 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, - 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, - 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 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, 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, + 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 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, 0x32, 0x0a, 0x15, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, + 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, + 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 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, 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 ( @@ -616,26 +420,23 @@ func file_controller_storage_target_store_v1_target_proto_rawDescGZIP() []byte { return file_controller_storage_target_store_v1_target_proto_rawDescData } -var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_controller_storage_target_store_v1_target_proto_goTypes = []interface{}{ (*TargetView)(nil), // 0: controller.storage.target.store.v1.TargetView (*TargetHostSet)(nil), // 1: controller.storage.target.store.v1.TargetHostSet - (*TcpTarget)(nil), // 2: controller.storage.target.store.v1.TcpTarget - (*CredentialLibrary)(nil), // 3: controller.storage.target.store.v1.CredentialLibrary - (*timestamp.Timestamp)(nil), // 4: controller.storage.timestamp.v1.Timestamp + (*CredentialLibrary)(nil), // 2: controller.storage.target.store.v1.CredentialLibrary + (*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_target_store_v1_target_proto_depIdxs = []int32{ - 4, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 3: controller.storage.target.store.v1.TcpTarget.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 4: controller.storage.target.store.v1.TcpTarget.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 5: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 3, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 3, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 3, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 3, // 3: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_controller_storage_target_store_v1_target_proto_init() } @@ -669,18 +470,6 @@ func file_controller_storage_target_store_v1_target_proto_init() { } } file_controller_storage_target_store_v1_target_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TcpTarget); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_controller_storage_target_store_v1_target_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CredentialLibrary); i { case 0: return &v.state @@ -699,7 +488,7 @@ func file_controller_storage_target_store_v1_target_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_target_store_v1_target_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/target/subtype.go b/internal/target/subtype.go deleted file mode 100644 index 79539757d..000000000 --- a/internal/target/subtype.go +++ /dev/null @@ -1,41 +0,0 @@ -package target - -import ( - "strings" -) - -type Subtype int - -const ( - UnknownSubtype Subtype = iota - TcpSubtype -) - -func (t Subtype) String() string { - switch t { - case TcpSubtype: - return "tcp" - } - return "unknown" -} - -// SubtypeFromType converts a string to a Subtype. -// returns UnknownSubtype if no Subtype with that name is found. -func SubtypeFromType(t string) Subtype { - switch { - case strings.EqualFold(strings.TrimSpace(t), TcpSubtype.String()): - return TcpSubtype - } - return UnknownSubtype -} - -// SubtypeFromId takes any public id in the target subsystem and uses the prefix to determine -// what subtype the id is for. -// Returns UnknownSubtype if no Subtype with this id's prefix is found. -func SubtypeFromId(id string) Subtype { - switch { - case strings.HasPrefix(strings.TrimSpace(id), TcpTargetPrefix): - return TcpSubtype - } - return UnknownSubtype -} diff --git a/internal/target/target.go b/internal/target/target.go index 7b27117d9..6916c43e0 100644 --- a/internal/target/target.go +++ b/internal/target/target.go @@ -1,12 +1,14 @@ package target import ( + "context" "fmt" "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/target/store" + "github.com/hashicorp/boundary/internal/types/subtypes" ) // Target is a commmon interface for all target subtypes @@ -17,29 +19,25 @@ type Target interface { GetName() string GetDescription() string GetVersion() uint32 - GetType() string + GetType() subtypes.Subtype GetCreateTime() *timestamp.Timestamp GetUpdateTime() *timestamp.Timestamp GetSessionMaxSeconds() uint32 GetSessionConnectionLimit() int32 GetWorkerFilter() string - oplog(op oplog.OpType) oplog.Metadata -} - -// TargetType defines the possible types for targets. -type TargetType uint32 - -const ( - UnknownTargetType TargetType = 0 - TcpTargetType TargetType = 1 -) - -// String returns a string representation of the target type. -func (t TargetType) String() string { - return [...]string{ - "unknown", - "tcp", - }[t] + Clone() Target + SetPublicId(context.Context, string) error + SetScopeId(string) + SetName(string) + SetDescription(string) + SetVersion(uint32) + SetDefaultPort(uint32) + SetCreateTime(*timestamp.Timestamp) + SetUpdateTime(*timestamp.Timestamp) + SetSessionMaxSeconds(uint32) + SetSessionConnectionLimit(int32) + SetWorkerFilter(string) + Oplog(op oplog.OpType) oplog.Metadata } const ( @@ -79,24 +77,32 @@ func (t *targetView) SetTableName(n string) { } } -// targetSubtype converts the target view to the concrete subtype -func (t *targetView) targetSubtype() (Target, error) { - const op = "target.targetView.targetSubtype" - switch t.Type { - case TcpTargetType.String(): - tcpTarget := allocTcpTarget() - tcpTarget.PublicId = t.PublicId - tcpTarget.ScopeId = t.ScopeId - tcpTarget.Name = t.Name - tcpTarget.Description = t.Description - tcpTarget.DefaultPort = t.DefaultPort - tcpTarget.CreateTime = t.CreateTime - tcpTarget.UpdateTime = t.UpdateTime - tcpTarget.Version = t.Version - tcpTarget.SessionMaxSeconds = t.SessionMaxSeconds - tcpTarget.SessionConnectionLimit = t.SessionConnectionLimit - tcpTarget.WorkerFilter = t.WorkerFilter - return &tcpTarget, nil - } - return nil, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("%s is an unknown target subtype of %s", t.PublicId, t.Type)) +func (t *targetView) Subtype() subtypes.Subtype { + return subtypes.Subtype(t.Type) +} + +// targetSubtype converts the target view to the concrete subtype +func (t *targetView) targetSubtype(ctx context.Context) (Target, error) { + const op = "target.targetView.targetSubtype" + + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) + if !ok { + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unknown target subtype of %s", t.PublicId, t.Type)) + } + + tt := alloc() + if err := tt.SetPublicId(ctx, t.PublicId); err != nil { + return nil, err + } + tt.SetVersion(t.Version) + tt.SetScopeId(t.ScopeId) + tt.SetName(t.Name) + tt.SetDescription(t.Description) + tt.SetDefaultPort(t.DefaultPort) + tt.SetCreateTime(t.CreateTime) + tt.SetUpdateTime(t.UpdateTime) + tt.SetSessionMaxSeconds(t.SessionMaxSeconds) + tt.SetSessionConnectionLimit(t.SessionConnectionLimit) + tt.SetWorkerFilter(t.WorkerFilter) + return tt, nil } diff --git a/internal/target/target_test.go b/internal/target/target_test.go index d3a287c38..a5366905d 100644 --- a/internal/target/target_test.go +++ b/internal/target/target_test.go @@ -1,15 +1,16 @@ -package target +package target_test import ( "testing" + "github.com/hashicorp/boundary/internal/target" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestTarget_SetTableName(t *testing.T) { t.Parallel() - defaultTableName := targetsViewDefaultTable + defaultTableName := target.TargetsViewDefaultTable tests := []struct { name string setNameTo string @@ -29,9 +30,9 @@ func TestTarget_SetTableName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - def := allocTargetView() + def := target.AllocTargetView() require.Equal(defaultTableName, def.TableName()) - s := allocTargetView() + s := target.AllocTargetView() s.SetTableName(tt.setNameTo) assert.Equal(tt.want, s.TableName()) }) diff --git a/internal/target/tcp/exports_test.go b/internal/target/tcp/exports_test.go new file mode 100644 index 000000000..023d2683c --- /dev/null +++ b/internal/target/tcp/exports_test.go @@ -0,0 +1,19 @@ +package tcp + +import "github.com/hashicorp/boundary/internal/target" + +// Expose functions and variables for tests. +var ( + TestId = testId + TestTargetName = testTargetName + DefaultTableName = defaultTableName +) + +// NewTestTarget is a test helper that bypasses the scopeId checks +// performed by NewTarget, allowing tests to create Targets with +// nil scopeIds for more robust testing. +func NewTestTarget(scopeId string, opt ...target.Option) *Target { + t, _ := New("testScope", opt...) + t.ScopeId = scopeId + return t +} diff --git a/internal/target/immutable_fields_test.go b/internal/target/tcp/immutable_fields_test.go similarity index 75% rename from internal/target/immutable_fields_test.go rename to internal/target/tcp/immutable_fields_test.go index 865f1d3a1..f268806e1 100644 --- a/internal/target/immutable_fields_test.go +++ b/internal/target/tcp/immutable_fields_test.go @@ -1,4 +1,4 @@ -package target +package tcp_test import ( "context" @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -24,17 +26,17 @@ func TestTarget_ImmutableFields(t *testing.T) { ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - new := TestTcpTarget(t, conn, proj.PublicId, testId(t)) + new := tcp.TestTarget(t, conn, proj.PublicId, tcp.TestId(t)) tests := []struct { name string - update *TcpTarget + update *tcp.Target fieldMask []string }{ { name: "public_id", - update: func() *TcpTarget { - target := new.Clone().(*TcpTarget) + update: func() *tcp.Target { + target := new.Clone().(*tcp.Target) target.PublicId = "p_thisIsNotAValidId" return target }(), @@ -42,8 +44,8 @@ func TestTarget_ImmutableFields(t *testing.T) { }, { name: "create time", - update: func() *TcpTarget { - target := new.Clone().(*TcpTarget) + update: func() *tcp.Target { + target := new.Clone().(*tcp.Target) target.CreateTime = &ts return target }(), @@ -51,8 +53,8 @@ func TestTarget_ImmutableFields(t *testing.T) { }, { name: "scope_id", - update: func() *TcpTarget { - target := new.Clone().(*TcpTarget) + update: func() *tcp.Target { + target := new.Clone().(*tcp.Target) target.ScopeId = "o_thisIsNotAValidId" return target }(), @@ -65,7 +67,7 @@ func TestTarget_ImmutableFields(t *testing.T) { assert, require := assert.New(t), require.New(t) orig := new.Clone() - orig.(*TcpTarget).SetTableName("target") + orig.(*tcp.Target).SetTableName("target") err := rw.LookupById(context.Background(), orig) require.NoError(err) @@ -75,11 +77,11 @@ func TestTarget_ImmutableFields(t *testing.T) { assert.Equal(0, rowsUpdated) after := new.Clone() - after.(*TcpTarget).SetTableName("target") + after.(*tcp.Target).SetTableName("target") err = rw.LookupById(context.Background(), after) require.NoError(err) - assert.True(proto.Equal(orig.(*TcpTarget), after.(*TcpTarget))) + assert.True(proto.Equal(orig.(*tcp.Target), after.(*tcp.Target))) }) } } @@ -93,17 +95,17 @@ func TestTcpTarget_ImmutableFields(t *testing.T) { ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - new := TestTcpTarget(t, conn, proj.PublicId, testId(t)) + new := tcp.TestTarget(t, conn, proj.PublicId, tcp.TestId(t)) tests := []struct { name string - update *TcpTarget + update *tcp.Target fieldMask []string }{ { name: "public_id", - update: func() *TcpTarget { - target := new.Clone().(*TcpTarget) + update: func() *tcp.Target { + target := new.Clone().(*tcp.Target) target.PublicId = "p_thisIsNotAValidId" return target }(), @@ -111,8 +113,8 @@ func TestTcpTarget_ImmutableFields(t *testing.T) { }, { name: "create time", - update: func() *TcpTarget { - target := new.Clone().(*TcpTarget) + update: func() *tcp.Target { + target := new.Clone().(*tcp.Target) target.CreateTime = &ts return target }(), @@ -120,8 +122,8 @@ func TestTcpTarget_ImmutableFields(t *testing.T) { }, { name: "scope_id", - update: func() *TcpTarget { - target := new.Clone().(*TcpTarget) + update: func() *tcp.Target { + target := new.Clone().(*tcp.Target) target.ScopeId = "o_thisIsNotAValidId" return target }(), @@ -144,7 +146,7 @@ func TestTcpTarget_ImmutableFields(t *testing.T) { err = rw.LookupById(context.Background(), after) require.NoError(err) - assert.True(proto.Equal(orig.(*TcpTarget), after.(*TcpTarget))) + assert.True(proto.Equal(orig.(*tcp.Target), after.(*tcp.Target))) }) } } @@ -155,35 +157,35 @@ func TestTargetHostSet_ImmutableFields(t *testing.T) { wrapper := db.TestWrapper(t) testKms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - projTarget := TestTcpTarget(t, conn, proj.PublicId, testId(t)) + projTarget := tcp.TestTarget(t, conn, proj.PublicId, tcp.TestId(t)) testCats := static.TestCatalogs(t, conn, proj.PublicId, 1) hsets := static.TestSets(t, conn, testCats[0].GetPublicId(), 2) require.Equal(t, 2, len(hsets)) - updateTarget := TestTcpTarget(t, conn, proj.PublicId, testId(t)) + updateTarget := tcp.TestTarget(t, conn, proj.PublicId, tcp.TestId(t)) updateHset := hsets[1] _, gotHostSources, _, err := repo.AddTargetHostSources(context.Background(), projTarget.PublicId, 1, []string{hsets[0].PublicId}) require.NoError(t, err) require.Equal(t, 1, len(gotHostSources)) - new, err := NewTargetHostSet(projTarget.PublicId, gotHostSources[0].Id()) + new, err := target.NewTargetHostSet(projTarget.PublicId, gotHostSources[0].Id()) require.NoError(t, err) tests := []struct { name string - update *TargetHostSet + update *target.TargetHostSet fieldMask []string }{ { name: "target_id", - update: func() *TargetHostSet { - target := new.Clone().(*TargetHostSet) + update: func() *target.TargetHostSet { + target := new.Clone().(*target.TargetHostSet) target.TargetId = updateTarget.PublicId return target }(), @@ -191,8 +193,8 @@ func TestTargetHostSet_ImmutableFields(t *testing.T) { }, { name: "create time", - update: func() *TargetHostSet { - target := new.Clone().(*TargetHostSet) + update: func() *target.TargetHostSet { + target := new.Clone().(*target.TargetHostSet) target.CreateTime = &ts return target }(), @@ -200,8 +202,8 @@ func TestTargetHostSet_ImmutableFields(t *testing.T) { }, { name: "host_set_id", - update: func() *TargetHostSet { - target := new.Clone().(*TargetHostSet) + update: func() *target.TargetHostSet { + target := new.Clone().(*target.TargetHostSet) target.HostSetId = updateHset.PublicId return target }(), @@ -223,7 +225,7 @@ func TestTargetHostSet_ImmutableFields(t *testing.T) { after := new.Clone() err = rw.LookupWhere(context.Background(), after, "target_id = ? and host_set_id = ?", new.TargetId, new.HostSetId) require.NoError(err) - assert.True(proto.Equal(orig.(*TargetHostSet), after.(*TargetHostSet))) + assert.True(proto.Equal(orig.(*target.TargetHostSet), after.(*target.TargetHostSet))) }) } } diff --git a/internal/target/tcp/register.go b/internal/target/tcp/register.go new file mode 100644 index 000000000..68508d430 --- /dev/null +++ b/internal/target/tcp/register.go @@ -0,0 +1,37 @@ +package tcp + +import ( + "context" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/target" +) + +func init() { + target.Register(Subtype, allocTarget, vet, TargetPrefix) +} + +const ( + // TargetPrefix is the prefix for public ids of a tcp.Target. + TargetPrefix = "ttcp" +) + +// vet validates that the given target.Target is a tcp.Target and that it +// has a Target store. +func vet(ctx context.Context, t target.Target) error { + const op = "tcp.vet" + + tt, ok := t.(*Target) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "target is not a tcp.Target") + } + + if tt == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing target") + } + + if tt.Target == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing target store") + } + return nil +} diff --git a/internal/target/repository_credential_source_test.go b/internal/target/tcp/repository_credential_source_test.go similarity index 89% rename from internal/target/repository_credential_source_test.go rename to internal/target/tcp/repository_credential_source_test.go index 4a0a39cc1..ba0720773 100644 --- a/internal/target/repository_credential_source_test.go +++ b/internal/target/tcp/repository_credential_source_test.go @@ -1,4 +1,4 @@ -package target +package tcp_test import ( "context" @@ -12,6 +12,8 @@ import ( "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -25,7 +27,7 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { testKms := kms.TestKms(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) _, staticProj := iam.TestScopes(t, iamRepo) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) cs := vault.TestCredentialStores(t, conn, wrapper, staticProj.GetPublicId(), 1)[0] @@ -103,7 +105,7 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - projTarget := TestTcpTarget(t, conn, staticProj.PublicId, tt.name) + projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, tt.name) gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, tt.args.targetVersion, tt.args.credLibIds) if tt.wantErr { require.Error(err) @@ -112,7 +114,7 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { } require.NoError(err) assert.Len(gotCredSources, len(tt.wantCredLibIds)) - gotCredSourcesMap := map[string]CredentialSource{} + gotCredSourcesMap := map[string]target.CredentialSource{} for _, s := range gotCredSources { gotCredSourcesMap[s.Id()] = s } @@ -124,26 +126,22 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { err = db.TestVerifyOplog(t, rw, projTarget.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) assert.NoError(err) - foundCredSources, err := fetchCredentialSources(context.Background(), rw, projTarget.PublicId) + tar, _, lookupCredSources, err := repo.LookupTarget(context.Background(), projTarget.PublicId) require.NoError(err) - assert.Len(foundCredSources, len(gotCredSourcesMap)) - for _, s := range foundCredSources { + assert.Equal(tt.args.targetVersion+1, tar.GetVersion()) + assert.Equal(projTarget.GetVersion(), tar.GetVersion()-1) + assert.True(proto.Equal(gotTarget.(*tcp.Target), tar.(*tcp.Target))) + assert.Equal(gotCredSources, lookupCredSources) + for _, s := range lookupCredSources { assert.NotEmpty(gotCredSourcesMap[s.Id()]) assert.Equal(projTarget.PublicId, s.TargetId()) } - - target, _, lookupCredSources, err := repo.LookupTarget(context.Background(), projTarget.PublicId) - require.NoError(err) - assert.Equal(tt.args.targetVersion+1, target.GetVersion()) - assert.Equal(projTarget.GetVersion(), target.GetVersion()-1) - assert.True(proto.Equal(gotTarget.(*TcpTarget), target.(*TcpTarget))) - assert.Equal(gotCredSources, lookupCredSources) }) } t.Run("add-existing", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - projTarget := TestTcpTarget(t, conn, staticProj.PublicId, "add-existing") + projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, "add-existing") _, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 1, []string{lib1.PublicId}) require.NoError(err) assert.Len(gotCredSources, 1) @@ -160,10 +158,10 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { assert.True(errors.Match(errors.T(errors.NotUnique), err)) // Previous transactions should have been rolled back and only lib1 should be associated - gotCredSources, err = fetchCredentialSources(context.Background(), rw, projTarget.PublicId) + _, _, lookupCredSources, err := repo.LookupTarget(context.Background(), projTarget.PublicId) require.NoError(err) - assert.Len(gotCredSources, 1) - assert.Equal(lib1.PublicId, gotCredSources[0].Id()) + assert.Len(lookupCredSources, 1) + assert.Equal(lib1.PublicId, lookupCredSources[0].Id()) }) t.Run("target-not-found", func(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -183,7 +181,7 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { testKms := kms.TestKms(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) _, proj := iam.TestScopes(t, iamRepo) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) type args struct { @@ -229,7 +227,7 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { { name: "not-found", args: args{ - targetIdOverride: func() *string { id := testId(t); return &id }(), + targetIdOverride: func() *string { id := tcp.TestId(t); return &id }(), createCnt: 5, deleteCnt: 5, }, @@ -277,7 +275,7 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { assert, require := assert.New(t), require.New(t) cs := css[i] - target := TestTcpTarget(t, conn, proj.PublicId, tt.name) + target := tcp.TestTarget(t, conn, proj.PublicId, tt.name) clIds := make([]string, 0, tt.args.createCnt) if tt.args.createCnt > 0 { @@ -334,7 +332,7 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { lib2 := libs[1] lib3 := libs[2] - projTarget := TestTcpTarget(t, conn, proj.PublicId, "add-existing") + projTarget := tcp.TestTarget(t, conn, proj.PublicId, "add-existing") _, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 1, []string{lib1.PublicId, lib2.PublicId}) require.NoError(err) assert.Len(gotCredSources, 2) @@ -352,9 +350,9 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { assert.Equal(0, delCount) // Previous transactions should have been rolled back and only lib1 should be associated - gotCredSources, err = fetchCredentialSources(context.Background(), rw, projTarget.PublicId) + _, _, lookupCredSources, err := repo.LookupTarget(context.Background(), projTarget.PublicId) require.NoError(err) - assert.Len(gotCredSources, 2) + assert.Len(lookupCredSources, 2) }) } @@ -364,7 +362,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) testKms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) iamRepo := iam.TestRepo(t, conn, wrapper) @@ -375,7 +373,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { lib1 := credLibs[0] lib2 := credLibs[1] - setupFn := func(target Target) []CredentialSource { + setupFn := func(target target.Target) []target.CredentialSource { credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 10) clIds := make([]string, 0, len(credLibs)) for _, cl := range credLibs { @@ -394,7 +392,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { } tests := []struct { name string - setup func(Target) []CredentialSource + setup func(target.Target) []target.CredentialSource args args wantAffectedRows int wantErr bool @@ -470,11 +468,11 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - target := TestTcpTarget(t, conn, proj.PublicId, tt.name) + tar := tcp.TestTarget(t, conn, proj.PublicId, tt.name) - var origCredSources []CredentialSource + var origCredSources []target.CredentialSource if tt.setup != nil { - origCredSources = tt.setup(target) + origCredSources = tt.setup(tar) } if tt.args.addToOrigLibs { origIds := make([]string, 0, len(origCredSources)) @@ -484,11 +482,11 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { tt.args.clIds = append(tt.args.clIds, origIds...) } - origTarget, _, lookupCredSources, err := repo.LookupTarget(context.Background(), target.GetPublicId()) + origTarget, _, lookupCredSources, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) require.NoError(err) assert.Equal(origCredSources, lookupCredSources) - _, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), target.GetPublicId(), tt.args.targetVersion, tt.args.clIds) + _, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, tt.args.clIds) if tt.wantErr { require.Error(err) assert.Equal(0, affectedRows) @@ -514,7 +512,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { sort.Strings(gotIds) assert.Equal(wantIds, gotIds) - foundTarget, _, _, err := repo.LookupTarget(context.Background(), target.GetPublicId()) + foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) require.NoError(err) if tt.name != "no-change" { assert.Equalf(tt.args.targetVersion+1, foundTarget.GetVersion(), "%s unexpected version: %d/%d", tt.name, tt.args.targetVersion+1, foundTarget.GetVersion()) diff --git a/internal/target/repository_host_source_test.go b/internal/target/tcp/repository_host_source_test.go similarity index 85% rename from internal/target/repository_host_source_test.go rename to internal/target/tcp/repository_host_source_test.go index b5edc579d..d0a2b3fa9 100644 --- a/internal/target/repository_host_source_test.go +++ b/internal/target/tcp/repository_host_source_test.go @@ -1,4 +1,4 @@ -package target +package tcp_test import ( "context" @@ -12,11 +12,20 @@ import ( "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target" + targetstore "github.com/hashicorp/boundary/internal/target/store" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) +func allocTargetHostSet() target.TargetHostSet { + return target.TargetHostSet{ + TargetHostSet: &targetstore.TargetHostSet{}, + } +} + func TestRepository_AddTargetHostSets(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") @@ -25,7 +34,7 @@ func TestRepository_AddTargetHostSets(t *testing.T) { testKms := kms.TestKms(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) staticOrg, staticProj := iam.TestScopes(t, iamRepo) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) createHostSetsFn := func(orgs, projects []string) []string { @@ -46,7 +55,7 @@ func TestRepository_AddTargetHostSets(t *testing.T) { type args struct { targetVersion uint32 wantTargetIds bool - opt []Option + opt []target.Option } tests := []struct { name string @@ -90,9 +99,9 @@ func TestRepository_AddTargetHostSets(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) require.NoError(conn.Where("1=1").Delete(allocTargetHostSet()).Error) - require.NoError(conn.Where("1=1").Delete(allocTcpTarget()).Error) + require.NoError(conn.Where("1=1").Delete(tcp.NewTestTarget("")).Error) - projTarget := TestTcpTarget(t, conn, staticProj.PublicId, "static-proj") + projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, "static-proj") var hostSourceIds []string origTarget, origHostSet, _, err := repo.LookupTarget(context.Background(), projTarget.PublicId) @@ -124,7 +133,7 @@ func TestRepository_AddTargetHostSets(t *testing.T) { return } require.NoError(err) - gotHostSet := map[string]HostSource{} + gotHostSet := map[string]target.HostSource{} for _, s := range gotHostSources { gotHostSet[s.Id()] = s } @@ -141,18 +150,15 @@ func TestRepository_AddTargetHostSets(t *testing.T) { err = db.TestVerifyOplog(t, rw, projTarget.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) assert.NoError(err) - foundHostSources, err := fetchHostSources(context.Background(), rw, projTarget.PublicId) + tar, ths, _, err := repo.LookupTarget(context.Background(), projTarget.PublicId) require.NoError(err) - for _, s := range foundHostSources { + assert.Equal(tt.args.targetVersion+1, tar.GetVersion()) + assert.Equal(origTarget.GetVersion(), tar.GetVersion()-1) + assert.Equal(gotHostSources, ths) + assert.True(proto.Equal(gotTarget.(*tcp.Target), tar.(*tcp.Target))) + for _, s := range ths { assert.NotEmpty(gotHostSet[s.Id()]) } - - target, ths, _, err := repo.LookupTarget(context.Background(), projTarget.PublicId) - require.NoError(err) - assert.Equal(tt.args.targetVersion+1, target.GetVersion()) - assert.Equal(origTarget.GetVersion(), target.GetVersion()-1) - assert.Equal(gotHostSources, ths) - assert.True(proto.Equal(gotTarget.(*TcpTarget), target.(*TcpTarget))) }) } t.Run("add-existing", func(t *testing.T) { @@ -164,7 +170,7 @@ func TestRepository_AddTargetHostSets(t *testing.T) { hs2 := hsets[1] hs3 := hsets[2] - projTarget := TestTcpTarget(t, conn, staticProj.PublicId, "add-existing") + projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, "add-existing") _, gotHostSources, _, err := repo.AddTargetHostSources(context.Background(), projTarget.PublicId, 1, []string{hs1.PublicId}) require.NoError(err) assert.Len(gotHostSources, 1) @@ -181,7 +187,7 @@ func TestRepository_AddTargetHostSets(t *testing.T) { assert.True(errors.Match(errors.T(errors.NotUnique), err)) // Previous transactions should have been rolled back and only hs1 should be associated - gotHostSources, err = fetchHostSources(context.Background(), rw, projTarget.PublicId) + _, gotHostSources, _, err = repo.LookupTarget(context.Background(), projTarget.PublicId) require.NoError(err) assert.Len(gotHostSources, 1) assert.Equal(hs1.PublicId, gotHostSources[0].Id()) @@ -196,16 +202,16 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { testKms := kms.TestKms(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) _, proj := iam.TestScopes(t, iamRepo) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) type args struct { - target Target + target target.Target targetIdOverride *string targetVersionOverride *uint32 createCnt int deleteCnt int - opt []Option + opt []target.Option } tests := []struct { name string @@ -217,7 +223,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { { name: "valid", args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "valid"), + target: tcp.TestTarget(t, conn, proj.PublicId, "valid"), createCnt: 5, deleteCnt: 5, }, @@ -227,7 +233,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { { name: "valid-keeping-some", args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "valid-keeping-some"), + target: tcp.TestTarget(t, conn, proj.PublicId, "valid-keeping-some"), createCnt: 5, deleteCnt: 2, }, @@ -237,7 +243,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { { name: "no-deletes", args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "no-deletes"), + target: tcp.TestTarget(t, conn, proj.PublicId, "no-deletes"), createCnt: 5, }, wantRowsDeleted: 0, @@ -247,8 +253,8 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { { name: "not-found", args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "not-found"), - targetIdOverride: func() *string { id := testId(t); return &id }(), + target: tcp.TestTarget(t, conn, proj.PublicId, "not-found"), + targetIdOverride: func() *string { id := tcp.TestId(t); return &id }(), createCnt: 5, deleteCnt: 5, }, @@ -258,7 +264,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { { name: "missing-target-id", args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "missing-target-id"), + target: tcp.TestTarget(t, conn, proj.PublicId, "missing-target-id"), targetIdOverride: func() *string { id := ""; return &id }(), createCnt: 5, deleteCnt: 5, @@ -270,7 +276,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { { name: "zero-version", args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "zero-version"), + target: tcp.TestTarget(t, conn, proj.PublicId, "zero-version"), targetVersionOverride: func() *uint32 { v := uint32(0); return &v }(), createCnt: 5, deleteCnt: 5, @@ -282,7 +288,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { { name: "bad-version", args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "bad-version"), + target: tcp.TestTarget(t, conn, proj.PublicId, "bad-version"), targetVersionOverride: func() *uint32 { v := uint32(1000); return &v }(), createCnt: 5, deleteCnt: 5, @@ -367,7 +373,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { hs2 := hsets[1] hs3 := hsets[2] - projTarget := TestTcpTarget(t, conn, proj.PublicId, "delete-unassociated") + projTarget := tcp.TestTarget(t, conn, proj.PublicId, "delete-unassociated") _, gotHostSources, _, err := repo.AddTargetHostSources(context.Background(), projTarget.PublicId, 1, []string{hs1.PublicId, hs2.PublicId}) require.NoError(err) assert.Len(gotHostSources, 2) @@ -386,7 +392,7 @@ func TestRepository_DeleteTargetHosts(t *testing.T) { assert.Equal(0, delCount) // Previous transactions should have been rolled back - gotHostSources, err = fetchHostSources(context.Background(), rw, projTarget.PublicId) + _, gotHostSources, _, err = repo.LookupTarget(context.Background(), projTarget.PublicId) require.NoError(err) assert.Len(gotHostSources, 2) }) @@ -398,7 +404,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) testKms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) iamRepo := iam.TestRepo(t, conn, wrapper) @@ -421,7 +427,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { return results } - setupFn := func(target Target) []HostSource { + setupFn := func(target target.Target) []target.HostSource { hs := createHostSetsFn() _, created, _, err := repo.AddTargetHostSources(context.Background(), target.GetPublicId(), 1, hs) require.NoError(t, err) @@ -429,15 +435,15 @@ func TestRepository_SetTargetHostSets(t *testing.T) { return created } type args struct { - target Target + target target.Target targetVersion uint32 hostSourceIds []string addToOrigHostSources bool - opt []Option + opt []target.Option } tests := []struct { name string - setup func(Target) []HostSource + setup func(target.Target) []target.HostSource args args wantAffectedRows int wantErr bool @@ -446,7 +452,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { name: "clear", setup: setupFn, args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "clear"), + target: tcp.TestTarget(t, conn, proj.PublicId, "clear"), targetVersion: 2, // yep, since setupFn will increment it to 2 hostSourceIds: []string{}, }, @@ -457,7 +463,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { name: "no-change", setup: setupFn, args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "no-change"), + target: tcp.TestTarget(t, conn, proj.PublicId, "no-change"), targetVersion: 2, // yep, since setupFn will increment it to 2 hostSourceIds: []string{}, addToOrigHostSources: true, @@ -469,7 +475,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { name: "add-sets", setup: setupFn, args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "add-sets"), + target: tcp.TestTarget(t, conn, proj.PublicId, "add-sets"), targetVersion: 2, // yep, since setupFn will increment it to 2 hostSourceIds: []string{testHostSetIds[0], testHostSetIds[1]}, addToOrigHostSources: true, @@ -481,7 +487,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { name: "add host sets with zero version", setup: setupFn, args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "add host sets with zero version"), + target: tcp.TestTarget(t, conn, proj.PublicId, "add host sets with zero version"), targetVersion: 0, hostSourceIds: []string{testHostSetIds[0], testHostSetIds[1]}, addToOrigHostSources: true, @@ -492,7 +498,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { name: "remove existing and add users and grps", setup: setupFn, args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "remove existing and add host sets"), + target: tcp.TestTarget(t, conn, proj.PublicId, "remove existing and add host sets"), targetVersion: 2, // yep, since setupFn will increment it to 2 hostSourceIds: []string{testHostSetIds[0], testHostSetIds[1]}, addToOrigHostSources: false, @@ -504,7 +510,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - var origHostSources []HostSource + var origHostSources []target.HostSource if tt.setup != nil { origHostSources = tt.setup(tt.args.target) } diff --git a/internal/target/repository_tcp_target_test.go b/internal/target/tcp/repository_tcp_target_test.go similarity index 68% rename from internal/target/repository_tcp_target_test.go rename to internal/target/tcp/repository_tcp_target_test.go index 89b34dfce..21b5806ed 100644 --- a/internal/target/repository_tcp_target_test.go +++ b/internal/target/tcp/repository_tcp_target_test.go @@ -1,4 +1,4 @@ -package target +package tcp_test import ( "context" @@ -13,18 +13,20 @@ import ( "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) -func TestRepository_CreateTcpTarget(t *testing.T) { +func TestRepository_CreateTarget(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) testKms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) @@ -43,8 +45,8 @@ func TestRepository_CreateTcpTarget(t *testing.T) { } type args struct { - target *TcpTarget - opt []Option + target *tcp.Target + opt []target.Option } tests := []struct { name string @@ -57,11 +59,11 @@ func TestRepository_CreateTcpTarget(t *testing.T) { { name: "valid-org", args: args{ - target: func() *TcpTarget { - target, err := NewTcpTarget(proj.PublicId, - WithName("valid-org"), - WithDescription("valid-org"), - WithDefaultPort(uint32(22))) + target: func() *tcp.Target { + target, err := tcp.New(proj.PublicId, + target.WithName("valid-org"), + target.WithDescription("valid-org"), + target.WithDefaultPort(uint32(22))) require.NoError(t, err) return target }(), @@ -73,15 +75,15 @@ func TestRepository_CreateTcpTarget(t *testing.T) { { name: "valid-org-with-host-sets", args: args{ - target: func() *TcpTarget { - target, err := NewTcpTarget(proj.PublicId, - WithName("valid-org-with-host-sets"), - WithDescription("valid-org"), - WithDefaultPort(uint32(22))) + target: func() *tcp.Target { + target, err := tcp.New(proj.PublicId, + target.WithName("valid-org-with-host-sets"), + target.WithDescription("valid-org"), + target.WithDefaultPort(uint32(22))) require.NoError(t, err) return target }(), - opt: []Option{WithHostSources(sets)}, + opt: []target.Option{target.WithHostSources(sets)}, }, wantErr: false, wantHostSources: sets, @@ -90,15 +92,15 @@ func TestRepository_CreateTcpTarget(t *testing.T) { { name: "valid-org-with-cred-libs", args: args{ - target: func() *TcpTarget { - target, err := NewTcpTarget(proj.PublicId, - WithName("valid-org-with-cred-libs"), - WithDescription("valid-org"), - WithDefaultPort(uint32(22))) + target: func() *tcp.Target { + target, err := tcp.New(proj.PublicId, + target.WithName("valid-org-with-cred-libs"), + target.WithDescription("valid-org"), + target.WithDefaultPort(uint32(22))) require.NoError(t, err) return target }(), - opt: []Option{WithCredentialSources(clIds)}, + opt: []target.Option{target.WithCredentialSources(clIds)}, }, wantErr: false, wantCredLibs: clIds, @@ -107,17 +109,17 @@ func TestRepository_CreateTcpTarget(t *testing.T) { { name: "valid-org-with-cred-libs-and-host-sets", args: args{ - target: func() *TcpTarget { - target, err := NewTcpTarget(proj.PublicId, - WithName("valid-org-with-cred-libs-and-host-sets"), - WithDescription("valid-org"), - WithDefaultPort(uint32(22))) + target: func() *tcp.Target { + target, err := tcp.New(proj.PublicId, + target.WithName("valid-org-with-cred-libs-and-host-sets"), + target.WithDescription("valid-org"), + target.WithDefaultPort(uint32(22))) require.NoError(t, err) return target }(), - opt: []Option{ - WithHostSources(sets), - WithCredentialSources(clIds), + opt: []target.Option{ + target.WithHostSources(sets), + target.WithCredentialSources(clIds), }, }, wantErr: false, @@ -135,8 +137,8 @@ func TestRepository_CreateTcpTarget(t *testing.T) { { name: "nil-target-store", args: args{ - target: func() *TcpTarget { - target := &TcpTarget{} + target: func() *tcp.Target { + target := &tcp.Target{} return target }(), }, @@ -146,13 +148,18 @@ func TestRepository_CreateTcpTarget(t *testing.T) { { name: "public-id-not-empty", args: args{ - target: func() *TcpTarget { - target, err := NewTcpTarget(proj.PublicId, WithName("valid-org"), WithDescription("valid-org"), WithDefaultPort(uint32(22))) + target: func() *tcp.Target { + tar, err := tcp.New( + proj.PublicId, + target.WithName("valid-org"), + target.WithDescription("valid-org"), + target.WithDefaultPort(uint32(22)), + ) require.NoError(t, err) - id, err := newTcpTargetId() + id, err := db.NewPublicId(tcp.TargetPrefix) require.NoError(t, err) - target.PublicId = id - return target + tar.PublicId = id + return tar }(), }, wantErr: true, @@ -161,11 +168,14 @@ func TestRepository_CreateTcpTarget(t *testing.T) { { name: "empty-scope-id", args: args{ - target: func() *TcpTarget { - target := allocTcpTarget() - target.Name = "empty-scope-id" + target: func() *tcp.Target { + tar, err := tcp.New( + proj.PublicId, + target.WithName("empty-scope-id"), + ) require.NoError(t, err) - return &target + tar.ScopeId = "" + return tar }(), }, wantErr: true, @@ -175,15 +185,15 @@ func TestRepository_CreateTcpTarget(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - target, hostSources, credSources, err := repo.CreateTcpTarget(context.Background(), tt.args.target, tt.args.opt...) + tar, hostSources, credSources, err := repo.CreateTarget(context.Background(), tt.args.target, tt.args.opt...) if tt.wantErr { assert.Error(err) - assert.Nil(target) + assert.Nil(tar) assert.True(errors.Match(errors.T(tt.wantIsError), err)) return } require.NoError(err) - assert.NotNil(target.GetPublicId()) + assert.NotNil(tar.GetPublicId()) hsIds := make([]string, 0, len(hostSources)) for _, s := range hostSources { hsIds = append(hsIds, s.Id()) @@ -196,20 +206,20 @@ func TestRepository_CreateTcpTarget(t *testing.T) { } assert.Equal(tt.wantCredLibs, clIds) - foundTarget, foundHostSources, foundCredLibs, err := repo.LookupTarget(context.Background(), target.GetPublicId()) + foundTarget, foundHostSources, foundCredLibs, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) assert.NoError(err) - assert.True(proto.Equal(target.(*TcpTarget), foundTarget.(*TcpTarget))) + assert.True(proto.Equal(tar.(*tcp.Target), foundTarget.(*tcp.Target))) assert.Equal(hostSources, foundHostSources) assert.Equal(credSources, foundCredLibs) - err = db.TestVerifyOplog(t, rw, target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + err = db.TestVerifyOplog(t, rw, tar.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) assert.NoError(err) // TODO (jimlambrt 9/2020) - unfortunately, we can currently // test to make sure that the oplog entry for a target host sets // create exist because the db.TestVerifyOplog doesn't really // support that level of testing and the previous call to - // CreateTcpTarget would create an oplog entry for the + // CreateTarget would create an oplog entry for the // create on the target even if no host sets were added. Once // TestVerifyOplog supports the appropriate granularity, we should // add an appropriate assert. @@ -225,9 +235,9 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { wrapper := db.TestWrapper(t) testKms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, testKms) + repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) - id := testId(t) + id := tcp.TestId(t) iamRepo := iam.TestRepo(t, conn, wrapper) _, proj := iam.TestScopes(t, iamRepo) @@ -238,7 +248,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { description string port uint32 fieldMaskPaths []string - opt []Option + opt []target.Option ScopeId string PublicId *string } @@ -246,7 +256,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { name string newScopeId string newName string - newTargetOpts []Option + newTargetOpts []target.Option args args wantRowsUpdate int wantErr bool @@ -288,7 +298,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { newScopeId: proj.PublicId, wantErr: true, wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTcpTarget: failed for 1: db.DoTx: target.(Repository).UpdateTcpTarget: target.(Repository).update: db.DoTx: target.(Repository).update: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100", + wantErrMsg: "target.(Repository).UpdateTarget: failed for 1: db.DoTx: target.(Repository).UpdateTarget: target.(Repository).update: db.DoTx: target.(Repository).update: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100", wantIsError: errors.RecordNotFound, }, { @@ -302,7 +312,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { newName: "null-name" + id, wantErr: true, wantRowsUpdate: 0, - wantErrMsg: "db.DoTx: target.(Repository).UpdateTcpTarget: target.(Repository).update: db.DoTx: target.(Repository).update: db.Update: name must not be empty: not null constraint violated: integrity violation: error #1001", + wantErrMsg: "db.DoTx: target.(Repository).UpdateTarget: target.(Repository).update: db.DoTx: target.(Repository).update: db.Update: name must not be empty: not null constraint violated: integrity violation: error #1001", }, { name: "null-description", @@ -312,7 +322,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { ScopeId: proj.PublicId, }, newScopeId: proj.PublicId, - newTargetOpts: []Option{WithDescription("null-description" + id)}, + newTargetOpts: []target.Option{target.WithDescription("null-description" + id)}, wantErr: false, wantRowsUpdate: 1, }, @@ -326,7 +336,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { newScopeId: proj.PublicId, wantErr: true, wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTcpTarget: empty field mask: parameter violation: error #104", + wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", wantIsError: errors.EmptyFieldMask, }, { @@ -339,7 +349,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { newScopeId: proj.PublicId, wantErr: true, wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTcpTarget: empty field mask: parameter violation: error #104", + wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", wantIsError: errors.EmptyFieldMask, }, { @@ -352,7 +362,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { newScopeId: proj.PublicId, wantErr: true, wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTcpTarget: invalid field mask: CreateTime: parameter violation: error #103", + wantErrMsg: "target.(Repository).UpdateTarget: invalid field mask: CreateTime: parameter violation: error #103", wantIsError: errors.InvalidFieldMask, }, { @@ -365,7 +375,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { newScopeId: proj.PublicId, wantErr: true, wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTcpTarget: invalid field mask: Alice: parameter violation: error #103", + wantErrMsg: "target.(Repository).UpdateTarget: invalid field mask: Alice: parameter violation: error #103", wantIsError: errors.InvalidFieldMask, }, { @@ -378,7 +388,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { }, newScopeId: proj.PublicId, wantErr: true, - wantErrMsg: "target.(Repository).UpdateTcpTarget: missing target public id: parameter violation: error #100", + wantErrMsg: "target.(Repository).UpdateTarget: missing target public id: parameter violation: error #100", wantIsError: errors.InvalidParameter, wantRowsUpdate: 0, }, @@ -390,7 +400,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { }, newScopeId: proj.PublicId, wantErr: true, - wantErrMsg: "target.(Repository).UpdateTcpTarget: empty field mask: parameter violation: error #104", + wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", wantIsError: errors.EmptyFieldMask, }, { @@ -424,7 +434,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { cs := css[i] require, assert := require.New(t), assert.New(t) if tt.wantDup { - _ = TestTcpTarget(t, conn, proj.PublicId, tt.args.name) + _ = tcp.TestTarget(t, conn, proj.PublicId, tt.args.name) } testCats := static.TestCatalogs(t, conn, proj.PublicId, 1) @@ -440,30 +450,31 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { testClIds = append(testClIds, cl.PublicId) } - tt.newTargetOpts = append(tt.newTargetOpts, WithHostSources(testHostSetIds), WithCredentialSources(testClIds)) + tt.newTargetOpts = append(tt.newTargetOpts, target.WithHostSources(testHostSetIds), target.WithCredentialSources(testClIds)) name := tt.newName if name == "" { - name = testId(t) + name = tcp.TestId(t) } - target := TestTcpTarget(t, conn, tt.newScopeId, name, tt.newTargetOpts...) - updateTarget := allocTcpTarget() - updateTarget.PublicId = target.PublicId + tar := tcp.TestTarget(t, conn, tt.newScopeId, name, tt.newTargetOpts...) + updateTarget := tcp.NewTestTarget( + tt.args.ScopeId, + target.WithName(tt.args.name), + target.WithDescription(tt.args.description), + target.WithDefaultPort(tt.args.port), + ) + updateTarget.PublicId = tar.PublicId if tt.args.PublicId != nil { updateTarget.PublicId = *tt.args.PublicId } - updateTarget.ScopeId = tt.args.ScopeId - updateTarget.Name = tt.args.name - updateTarget.Description = tt.args.description - updateTarget.DefaultPort = tt.args.port - targetAfterUpdate, hostSources, credSources, updatedRows, err := repo.UpdateTcpTarget(context.Background(), &updateTarget, target.Version, tt.args.fieldMaskPaths, tt.args.opt...) + targetAfterUpdate, hostSources, credSources, updatedRows, err := repo.UpdateTarget(context.Background(), updateTarget, tar.Version, tt.args.fieldMaskPaths, tt.args.opt...) if tt.wantErr { assert.Error(err) assert.True(errors.Match(errors.T(tt.wantIsError), err)) assert.Nil(targetAfterUpdate) assert.Equal(0, updatedRows) assert.Contains(err.Error(), tt.wantErrMsg) - err = db.TestVerifyOplog(t, rw, target.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) + err = db.TestVerifyOplog(t, rw, tar.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) assert.Error(err) assert.True(errors.IsNotFoundError(err)) return @@ -485,13 +496,13 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { switch tt.name { case "valid-no-op": - assert.Equal(target.UpdateTime, targetAfterUpdate.(*TcpTarget).UpdateTime) + assert.Equal(tar.UpdateTime, targetAfterUpdate.(*tcp.Target).UpdateTime) default: - assert.NotEqual(target.UpdateTime, targetAfterUpdate.(*TcpTarget).UpdateTime) + assert.NotEqual(tar.UpdateTime, targetAfterUpdate.(*tcp.Target).UpdateTime) } - foundTarget, _, _, err := repo.LookupTarget(context.Background(), target.PublicId) + foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.PublicId) assert.NoError(err) - assert.True(proto.Equal(targetAfterUpdate.((*TcpTarget)), foundTarget.((*TcpTarget)))) + assert.True(proto.Equal(targetAfterUpdate.((*tcp.Target)), foundTarget.((*tcp.Target)))) underlyingDB, err := conn.SqlDB(ctx) require.NoError(err) dbassert := dbassert.New(t, underlyingDB) @@ -499,7 +510,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { assert.Equal(foundTarget.GetDescription(), "") dbassert.IsNull(foundTarget, "description") } - err = db.TestVerifyOplog(t, rw, target.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) + err = db.TestVerifyOplog(t, rw, tar.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) assert.NoError(err) }) } diff --git a/internal/target/tcp/repository_test.go b/internal/target/tcp/repository_test.go new file mode 100644 index 000000000..32501047b --- /dev/null +++ b/internal/target/tcp/repository_test.go @@ -0,0 +1,347 @@ +package tcp_test + +import ( + "context" + "fmt" + "strconv" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_LookupTarget(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + proj.Name = "project-name" + _, _, err := iamRepo.UpdateScope(context.Background(), proj, 1, []string{"name"}) + require.NoError(t, err) + rw := db.New(conn) + repo, err := target.NewRepository(rw, rw, testKms) + require.NoError(t, err) + tgt := tcp.TestTarget(t, conn, proj.PublicId, "target-name") + + tests := []struct { + testName string + id string + name string + scopeId string + scopeName string + wantErr bool + }{ + { + testName: "id", + id: tgt.PublicId, + wantErr: false, + }, + { + testName: "name only", + name: tgt.Name, + wantErr: true, + }, + { + testName: "scope id only", + scopeId: proj.PublicId, + wantErr: true, + }, + { + testName: "scope name only", + scopeName: proj.Name, + wantErr: true, + }, + { + testName: "scope name and id", + scopeId: proj.PublicId, + scopeName: proj.Name, + wantErr: true, + }, + { + testName: "everything", + name: tgt.Name, + scopeId: proj.PublicId, + scopeName: proj.Name, + wantErr: true, + }, + { + testName: "name and scope name", + name: tgt.Name, + scopeName: proj.Name, + wantErr: false, + }, + { + testName: "name and scope id", + name: tgt.Name, + scopeId: proj.PublicId, + wantErr: false, + }, + { + testName: "id and name", + id: tgt.PublicId, + name: tgt.Name, + scopeId: proj.PublicId, + wantErr: true, + }, + { + testName: "id and scope name", + id: tgt.PublicId, + scopeName: proj.Name, + wantErr: true, + }, + { + testName: "id and scope id", + id: tgt.PublicId, + scopeId: proj.PublicId, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + id := tt.id + if tt.name != "" && tt.id == "" { + id = tt.name + } + var opts []target.Option + if tt.name != "" { + opts = append(opts, target.WithName(tt.name)) + } + if tt.scopeId != "" { + opts = append(opts, target.WithScopeId(tt.scopeId)) + } + if tt.scopeName != "" { + opts = append(opts, target.WithScopeName(tt.scopeName)) + } + got, _, _, err := repo.LookupTarget(context.Background(), id, opts...) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + assert.Equal(tgt.PublicId, got.GetPublicId()) + }) + } +} + +func TestRepository_ListTargets(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + const testLimit = 10 + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repo, err := target.NewRepository(rw, rw, testKms, target.WithLimit(testLimit)) + require.NoError(t, err) + + type args struct { + opt []target.Option + } + tests := []struct { + name string + createCnt int + createScopeId string + createScopeId2 string + grantUserId string + args args + wantCnt int + wantErr bool + }{ + { + name: "tcp-target", + createCnt: 5, + createScopeId: proj.PublicId, + args: args{ + opt: []target.Option{target.WithType(tcp.Subtype), target.WithScopeIds([]string{proj.PublicId})}, + }, + wantCnt: 5, + wantErr: false, + }, + { + name: "no-limit", + createCnt: testLimit + 1, + createScopeId: proj.PublicId, + args: args{ + opt: []target.Option{target.WithLimit(-1), target.WithScopeIds([]string{proj.PublicId})}, + }, + wantCnt: testLimit + 1, + wantErr: false, + }, + { + name: "default-limit", + createCnt: testLimit + 1, + createScopeId: proj.PublicId, + args: args{ + opt: []target.Option{target.WithScopeIds([]string{proj.PublicId})}, + }, + wantCnt: testLimit, + wantErr: false, + }, + { + name: "custom-limit", + createCnt: testLimit + 1, + createScopeId: proj.PublicId, + args: args{ + opt: []target.Option{target.WithLimit(3), target.WithScopeIds([]string{proj.PublicId})}, + }, + wantCnt: 3, + wantErr: false, + }, + { + name: "bad-org", + createCnt: 1, + createScopeId: proj.PublicId, + args: args{ + opt: []target.Option{target.WithScopeIds([]string{"bad-id"})}, + }, + wantCnt: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + require.NoError(conn.Where("1=1").Delete(tcp.NewTestTarget("")).Error) + testGroups := []*tcp.Target{} + for i := 0; i < tt.createCnt; i++ { + switch { + case tt.createScopeId2 != "" && i%2 == 0: + testGroups = append(testGroups, tcp.TestTarget(t, conn, tt.createScopeId2, strconv.Itoa(i))) + default: + testGroups = append(testGroups, tcp.TestTarget(t, conn, tt.createScopeId, strconv.Itoa(i))) + } + } + assert.Equal(tt.createCnt, len(testGroups)) + got, err := repo.ListTargets(context.Background(), tt.args.opt...) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + assert.Equal(tt.wantCnt, len(got)) + }) + } +} + +func TestRepository_ListRoles_Multiple_Scopes(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj1 := iam.TestScopes(t, iamRepo) + _, proj2 := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repo, err := target.NewRepository(rw, rw, testKms) + require.NoError(t, err) + + require.NoError(t, conn.Where("1=1").Delete(tcp.NewTestTarget("")).Error) + + const numPerScope = 10 + var total int + for i := 0; i < numPerScope; i++ { + tcp.TestTarget(t, conn, proj1.GetPublicId(), fmt.Sprintf("proj1-%d", i)) + total++ + tcp.TestTarget(t, conn, proj2.GetPublicId(), fmt.Sprintf("proj2-%d", i)) + total++ + } + + got, err := repo.ListTargets(context.Background(), target.WithScopeIds([]string{"global", proj1.GetPublicId(), proj2.GetPublicId()})) + require.NoError(t, err) + assert.Equal(t, total, len(got)) +} + +func TestRepository_DeleteTarget(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + repo, err := target.NewRepository(rw, rw, testKms) + require.NoError(t, err) + + type args struct { + target target.Target + opt []target.Option + } + tests := []struct { + name string + args args + wantRowsDeleted int + wantErr bool + wantErrMsg string + }{ + { + name: "valid", + args: args{ + target: tcp.TestTarget(t, conn, proj.PublicId, "valid"), + }, + wantRowsDeleted: 1, + wantErr: false, + }, + { + name: "no-public-id", + args: args{ + target: func() target.Target { + tar, _ := tcp.New(proj.PublicId) + return tar + }(), + }, + wantRowsDeleted: 0, + wantErr: true, + wantErrMsg: "target.(Repository).DeleteTarget: missing public id: parameter violation: error #100", + }, + { + name: "not-found", + args: args{ + target: func() target.Target { + id, err := db.NewPublicId(tcp.TargetPrefix) + require.NoError(t, err) + tar, _ := tcp.New(proj.PublicId) + tar.PublicId = id + return tar + }(), + }, + wantRowsDeleted: 0, + wantErr: true, + wantErrMsg: "db.LookupById: record not found, search issue: error #1100", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + deletedRows, err := repo.DeleteTarget(context.Background(), tt.args.target.GetPublicId(), tt.args.opt...) + if tt.wantErr { + assert.Error(err) + assert.Equal(0, deletedRows) + assert.Contains(err.Error(), tt.wantErrMsg) + err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.IsNotFoundError(err)) + return + } + assert.NoError(err) + assert.Equal(tt.wantRowsDeleted, deletedRows) + foundGroup, _, _, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) + assert.NoError(err) + assert.Nil(foundGroup) + + err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) + assert.NoError(err) + }) + } +} diff --git a/internal/target/tcp/store/target.pb.go b/internal/target/tcp/store/target.pb.go new file mode 100644 index 000000000..903fe7f15 --- /dev/null +++ b/internal/target/tcp/store/target.pb.go @@ -0,0 +1,306 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/storage/target/tcp/store/v1/target.proto + +package store + +import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Target struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is used to access the tcp.Target via an API + // @inject_tag: gorm:"primary_key" + PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // scope id for the tcp.Target + // @inject_tag: `gorm:"default:null"` + ScopeId string `protobuf:"bytes,20,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"` + // name is the optional friendly name used to + // access the tcp.Target via an API + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,30,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description of the tcp.Target + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,40,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,50,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // update_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,60,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // version allows optimistic locking of the tcp.Target when modifying the + // tcp.Target + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // default port of the tcp.Target + // @inject_tag: `gorm:"default:null"` + DefaultPort uint32 `protobuf:"varint,80,opt,name=default_port,json=defaultPort,proto3" json:"default_port,omitempty" gorm:"default:null"` + // Maximum total lifetime of a created session, in seconds + // @inject_tag: `gorm:"default:null"` + SessionMaxSeconds uint32 `protobuf:"varint,100,opt,name=session_max_seconds,json=sessionMaxSeconds,proto3" json:"session_max_seconds,omitempty" gorm:"default:null"` + // Maximum number of connections in a session + // @inject_tag: `gorm:"default:null"` + SessionConnectionLimit int32 `protobuf:"varint,110,opt,name=session_connection_limit,json=sessionConnectionLimit,proto3" json:"session_connection_limit,omitempty" gorm:"default:null"` + // A boolean expression that allows filtering the workers that can handle a session + // @inject_tag: `gorm:"default:null"` + WorkerFilter string `protobuf:"bytes,120,opt,name=worker_filter,json=workerFilter,proto3" json:"worker_filter,omitempty" gorm:"default:null"` +} + +func (x *Target) Reset() { + *x = Target{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_target_tcp_store_v1_target_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Target) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Target) ProtoMessage() {} + +func (x *Target) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_target_tcp_store_v1_target_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Target.ProtoReflect.Descriptor instead. +func (*Target) Descriptor() ([]byte, []int) { + return file_controller_storage_target_tcp_store_v1_target_proto_rawDescGZIP(), []int{0} +} + +func (x *Target) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Target) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *Target) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Target) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Target) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *Target) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *Target) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Target) GetDefaultPort() uint32 { + if x != nil { + return x.DefaultPort + } + return 0 +} + +func (x *Target) GetSessionMaxSeconds() uint32 { + if x != nil { + return x.SessionMaxSeconds + } + return 0 +} + +func (x *Target) GetSessionConnectionLimit() int32 { + if x != nil { + return x.SessionConnectionLimit + } + return 0 +} + +func (x *Target) GetWorkerFilter() string { + if x != nil { + return x.WorkerFilter + } + return "" +} + +var File_controller_storage_target_tcp_store_v1_target_proto protoreflect.FileDescriptor + +var file_controller_storage_target_tcp_store_v1_target_proto_rawDesc = []byte{ + 0x0a, 0x33, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x74, 0x63, 0x70, 0x2f, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x26, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x2e, 0x74, 0x63, 0x70, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x05, 0x0a, 0x06, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, + 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x32, 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, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x3c, 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, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4d, 0x0a, 0x0c, 0x64, 0x65, 0x66, + 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x42, + 0x2a, 0xc2, 0xdd, 0x29, 0x26, 0x0a, 0x0b, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x6f, + 0x72, 0x74, 0x12, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x64, 0x65, 0x66, + 0x61, 0x75, 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x5c, 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x64, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x2c, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x11, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x13, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x52, 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x70, 0x0a, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x05, 0x42, 0x36, 0xc2, 0xdd, 0x29, 0x32, 0x0a, 0x16, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x52, 0x16, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x46, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x21, 0xc2, 0xdd, 0x29, 0x1d, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x12, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x42, 0x3f, 0x5a, 0x3d, 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, 0x74, 0x63, 0x70, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_storage_target_tcp_store_v1_target_proto_rawDescOnce sync.Once + file_controller_storage_target_tcp_store_v1_target_proto_rawDescData = file_controller_storage_target_tcp_store_v1_target_proto_rawDesc +) + +func file_controller_storage_target_tcp_store_v1_target_proto_rawDescGZIP() []byte { + file_controller_storage_target_tcp_store_v1_target_proto_rawDescOnce.Do(func() { + file_controller_storage_target_tcp_store_v1_target_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_target_tcp_store_v1_target_proto_rawDescData) + }) + return file_controller_storage_target_tcp_store_v1_target_proto_rawDescData +} + +var file_controller_storage_target_tcp_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_storage_target_tcp_store_v1_target_proto_goTypes = []interface{}{ + (*Target)(nil), // 0: controller.storage.target.tcp.store.v1.Target + (*timestamp.Timestamp)(nil), // 1: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_target_tcp_store_v1_target_proto_depIdxs = []int32{ + 1, // 0: controller.storage.target.tcp.store.v1.Target.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 1, // 1: controller.storage.target.tcp.store.v1.Target.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_controller_storage_target_tcp_store_v1_target_proto_init() } +func file_controller_storage_target_tcp_store_v1_target_proto_init() { + if File_controller_storage_target_tcp_store_v1_target_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_target_tcp_store_v1_target_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Target); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_target_tcp_store_v1_target_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_target_tcp_store_v1_target_proto_goTypes, + DependencyIndexes: file_controller_storage_target_tcp_store_v1_target_proto_depIdxs, + MessageInfos: file_controller_storage_target_tcp_store_v1_target_proto_msgTypes, + }.Build() + File_controller_storage_target_tcp_store_v1_target_proto = out.File + file_controller_storage_target_tcp_store_v1_target_proto_rawDesc = nil + file_controller_storage_target_tcp_store_v1_target_proto_goTypes = nil + file_controller_storage_target_tcp_store_v1_target_proto_depIdxs = nil +} diff --git a/internal/target/tcp/target.go b/internal/target/tcp/target.go new file mode 100644 index 000000000..e98a7f329 --- /dev/null +++ b/internal/target/tcp/target.go @@ -0,0 +1,173 @@ +// Package tcp provides a Target subtype for a TCP Target. +// Importing this package will register it with the target package and +// allow the target.Repository to support tcp.Targets. +package tcp + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp/store" + "github.com/hashicorp/boundary/internal/types/subtypes" + "google.golang.org/protobuf/proto" +) + +const ( + defaultTableName = "target_tcp" + Subtype = subtypes.Subtype("tcp") +) + +// Target is a resources that represets a networked service +// that can be accessed via TCP. It is a subtype of target.Target. +type Target struct { + *store.Target + tableName string `gorm:"-"` +} + +// Ensure Target implements interfaces +var ( + _ target.Target = (*Target)(nil) + _ db.VetForWriter = (*Target)(nil) + _ oplog.ReplayableMessage = (*Target)(nil) +) + +// New creates a new in memory tcp target. WithName, WithDescription and +// WithDefaultPort options are supported +func New(scopeId string, opt ...target.Option) (*Target, error) { + const op = "tcp.NewTarget" + opts := target.GetOpts(opt...) + if scopeId == "" { + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing scope id") + } + t := &Target{ + Target: &store.Target{ + ScopeId: scopeId, + Name: opts.WithName, + Description: opts.WithDescription, + DefaultPort: opts.WithDefaultPort, + SessionConnectionLimit: opts.WithSessionConnectionLimit, + SessionMaxSeconds: opts.WithSessionMaxSeconds, + WorkerFilter: opts.WithWorkerFilter, + }, + } + return t, nil +} + +// allocTarget will allocate a tcp target +func allocTarget() target.Target { + return &Target{ + Target: &store.Target{}, + } +} + +// Clone creates a clone of the Target +func (t *Target) Clone() target.Target { + cp := proto.Clone(t.Target) + return &Target{ + Target: cp.(*store.Target), + } +} + +// VetForWrite implements db.VetForWrite() interface and validates the tcp target +// before it's written. +func (t *Target) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error { + const op = "tcp.(Target).VetForWrite" + if t.PublicId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing public id") + } + if opType == db.CreateOp { + if t.ScopeId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + if t.Name == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing name") + } + } + return nil +} + +// TableName returns the tablename to override the default gorm table name +func (t *Target) TableName() string { + if t.tableName != "" { + return t.tableName + } + return defaultTableName +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (t *Target) SetTableName(n string) { + t.tableName = n +} + +// Oplog provides the oplog.Metadata for recording operations taken on a Target. +func (t *Target) Oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{t.PublicId}, + "resource-type": []string{"tcp target"}, + "op-type": []string{op.String()}, + "scope-id": []string{t.ScopeId}, + } + return metadata +} + +func (t *Target) GetType() subtypes.Subtype { + return Subtype +} + +func (t *Target) SetPublicId(ctx context.Context, publicId string) error { + const op = "tcp.(Target).SetPublicId" + if !strings.HasPrefix(publicId, TargetPrefix+"_") { + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("passed-in public ID %q has wrong prefix, should be %q", publicId, TargetPrefix)) + } + + t.PublicId = publicId + return nil +} + +func (t *Target) SetScopeId(scopeId string) { + t.ScopeId = scopeId +} + +func (t *Target) SetName(name string) { + t.Name = name +} + +func (t *Target) SetDescription(description string) { + t.Description = description +} + +func (t *Target) SetVersion(v uint32) { + t.Version = v +} + +func (t *Target) SetDefaultPort(port uint32) { + t.DefaultPort = port +} + +func (t *Target) SetCreateTime(ts *timestamp.Timestamp) { + t.CreateTime = ts +} + +func (t *Target) SetUpdateTime(ts *timestamp.Timestamp) { + t.UpdateTime = ts +} + +func (t *Target) SetSessionMaxSeconds(s uint32) { + t.SessionMaxSeconds = s +} + +func (t *Target) SetSessionConnectionLimit(limit int32) { + t.SessionConnectionLimit = limit +} + +func (t *Target) SetWorkerFilter(filter string) { + t.WorkerFilter = filter +} diff --git a/internal/target/tcp_target_test.go b/internal/target/tcp/target_test.go similarity index 71% rename from internal/target/tcp_target_test.go rename to internal/target/tcp/target_test.go index faa059033..82ec6b46c 100644 --- a/internal/target/tcp_target_test.go +++ b/internal/target/tcp/target_test.go @@ -1,4 +1,4 @@ -package target +package tcp_test import ( "context" @@ -10,24 +10,26 @@ import ( "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) -func TestTcpTarget_Create(t *testing.T) { +func TestTarget_Create(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) type args struct { scopeId string - opt []Option + opt []target.Option } tests := []struct { name string args args - want *TcpTarget + want *tcp.Target wantErr bool wantIsErr errors.Code create bool @@ -43,15 +45,16 @@ func TestTcpTarget_Create(t *testing.T) { name: "valid-proj-scope", args: args{ scopeId: prj.PublicId, - opt: []Option{WithName("valid-proj-scope")}, + opt: []target.Option{target.WithName("valid-proj-scope")}, }, - want: func() *TcpTarget { - t := allocTcpTarget() - t.ScopeId = prj.PublicId - t.Name = "valid-proj-scope" - t.SessionMaxSeconds = uint32((8 * time.Hour).Seconds()) - t.SessionConnectionLimit = 1 - return &t + want: func() *tcp.Target { + t, _ := tcp.New( + prj.PublicId, + target.WithName("valid-proj-scope"), + target.WithSessionMaxSeconds(uint32((8 * time.Hour).Seconds())), + target.WithSessionConnectionLimit(1), + ) + return t }(), create: true, }, @@ -59,7 +62,7 @@ func TestTcpTarget_Create(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := NewTcpTarget(tt.args.scopeId, tt.args.opt...) + got, err := tcp.New(tt.args.scopeId, tt.args.opt...) if tt.wantErr { require.Error(err) assert.True(errors.Match(errors.T(tt.wantIsErr), err)) @@ -68,22 +71,22 @@ func TestTcpTarget_Create(t *testing.T) { require.NoError(err) assert.Equal(tt.want, got) if tt.create { - id, err := newTcpTargetId() + id, err := db.NewPublicId(tcp.TargetPrefix) require.NoError(err) got.PublicId = id err = db.New(conn).Create(context.Background(), got) if tt.wantCreateErr { assert.Error(err) return - } else { - assert.NoError(err) } + + assert.NoError(err) } }) } } -func TestTcpTarget_Delete(t *testing.T) { +func TestTarget_Delete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) @@ -92,27 +95,27 @@ func TestTcpTarget_Delete(t *testing.T) { tests := []struct { name string - target *TcpTarget + target *tcp.Target wantRowsDeleted int wantErr bool wantErrMsg string }{ { name: "valid", - target: TestTcpTarget(t, conn, proj.PublicId, testTargetName(t, proj.PublicId)), + target: tcp.TestTarget(t, conn, proj.PublicId, tcp.TestTargetName(t, proj.PublicId)), wantErr: false, wantRowsDeleted: 1, }, { name: "bad-id", - target: func() *TcpTarget { - target := allocTcpTarget() - id, err := newTcpTargetId() + target: func() *tcp.Target { + tar, _ := tcp.New(proj.PublicId) + + id, err := db.NewPublicId(tcp.TargetPrefix) require.NoError(t, err) - target.PublicId = id - target.ScopeId = proj.PublicId - target.Name = testTargetName(t, proj.PublicId) - return &target + tar.PublicId = id + tar.Name = tcp.TestTargetName(t, proj.PublicId) + return tar }(), wantErr: false, wantRowsDeleted: 0, @@ -121,9 +124,9 @@ func TestTcpTarget_Delete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - deleteTarget := allocTcpTarget() + deleteTarget := tcp.NewTestTarget("") deleteTarget.PublicId = tt.target.PublicId - deletedRows, err := rw.Delete(context.Background(), &deleteTarget) + deletedRows, err := rw.Delete(context.Background(), deleteTarget) if tt.wantErr { require.Error(err) return @@ -134,18 +137,18 @@ func TestTcpTarget_Delete(t *testing.T) { return } assert.Equal(tt.wantRowsDeleted, deletedRows) - foundTarget := allocTcpTarget() + foundTarget := tcp.NewTestTarget("") foundTarget.PublicId = tt.target.PublicId - err = rw.LookupById(context.Background(), &foundTarget) + err = rw.LookupById(context.Background(), foundTarget) require.Error(err) assert.True(errors.IsNotFoundError(err)) }) } } -func TestTcpTarget_Update(t *testing.T) { +func TestTarget_Update(t *testing.T) { t.Parallel() - id := testId(t) + id := tcp.TestId(t) ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) @@ -246,37 +249,36 @@ func TestTcpTarget_Update(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) if tt.wantDup { - target := TestTcpTarget(t, conn, proj.PublicId, testTargetName(t, proj.PublicId)) + target := tcp.TestTarget(t, conn, proj.PublicId, tcp.TestTargetName(t, proj.PublicId)) target.Name = tt.args.name _, err := rw.Update(context.Background(), target, tt.args.fieldMaskPaths, tt.args.nullPaths) require.NoError(err) } - id := testId(t) - target := TestTcpTarget(t, conn, proj.PublicId, id, WithDescription(id)) + id := tcp.TestId(t) + tar := tcp.TestTarget(t, conn, proj.PublicId, id, target.WithDescription(id)) - updateTarget := allocTcpTarget() - updateTarget.PublicId = target.PublicId - updateTarget.ScopeId = tt.args.ScopeId + updateTarget := tcp.NewTestTarget(tt.args.ScopeId) + updateTarget.PublicId = tar.PublicId updateTarget.Name = tt.args.name updateTarget.Description = tt.args.description - updatedRows, err := rw.Update(context.Background(), &updateTarget, tt.args.fieldMaskPaths, tt.args.nullPaths) + updatedRows, err := rw.Update(context.Background(), updateTarget, tt.args.fieldMaskPaths, tt.args.nullPaths) if tt.wantErr { require.Error(err) assert.Equal(0, updatedRows) assert.Equal(tt.wantErrMsg, err.Error()) - err = db.TestVerifyOplog(t, rw, target.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) + err = db.TestVerifyOplog(t, rw, tar.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) require.Error(err) assert.Contains(err.Error(), "record not found") return } require.NoError(err) assert.Equal(tt.wantRowsUpdate, updatedRows) - assert.NotEqual(target.UpdateTime, updateTarget.UpdateTime) - foundTarget := allocTcpTarget() - foundTarget.PublicId = target.GetPublicId() - err = rw.LookupByPublicId(context.Background(), &foundTarget) + assert.NotEqual(tar.UpdateTime, updateTarget.UpdateTime) + foundTarget := tcp.NewTestTarget(tt.args.ScopeId) + foundTarget.PublicId = tar.GetPublicId() + err = rw.LookupByPublicId(context.Background(), foundTarget) require.NoError(err) assert.True(proto.Equal(updateTarget, foundTarget)) if len(tt.args.nullPaths) != 0 { @@ -291,49 +293,49 @@ func TestTcpTarget_Update(t *testing.T) { } t.Run("update dup names in diff scopes", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - id := testId(t) + id := tcp.TestId(t) _, proj2 := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - _ = TestTcpTarget(t, conn, proj2.PublicId, id, WithDescription(id)) - projTarget := TestTcpTarget(t, conn, proj.PublicId, id) + _ = tcp.TestTarget(t, conn, proj2.PublicId, id, target.WithDescription(id)) + projTarget := tcp.TestTarget(t, conn, proj.PublicId, id) projTarget.Name = id updatedRows, err := rw.Update(context.Background(), projTarget, []string{"Name"}, nil) require.NoError(err) assert.Equal(1, updatedRows) - foundTarget := allocTcpTarget() + foundTarget, _ := tcp.New(proj2.PublicId) foundTarget.PublicId = projTarget.GetPublicId() - err = rw.LookupByPublicId(context.Background(), &foundTarget) + err = rw.LookupByPublicId(context.Background(), foundTarget) require.NoError(err) assert.Equal(id, projTarget.Name) }) } -func TestTcpTarget_Clone(t *testing.T) { +func TestTarget_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) t.Run("valid", func(t *testing.T) { assert := assert.New(t) _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - target := TestTcpTarget(t, conn, proj.PublicId, testTargetName(t, proj.PublicId)) - cp := target.Clone() - assert.True(proto.Equal(cp.(*TcpTarget).TcpTarget, target.TcpTarget)) + tar := tcp.TestTarget(t, conn, proj.PublicId, tcp.TestTargetName(t, proj.PublicId)) + cp := tar.Clone() + assert.True(proto.Equal(cp.(*tcp.Target).Target, tar.Target)) }) t.Run("not-equal", func(t *testing.T) { assert := assert.New(t) _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) _, proj2 := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - target := TestTcpTarget(t, conn, proj.PublicId, testTargetName(t, proj.PublicId)) - target2 := TestTcpTarget(t, conn, proj2.PublicId, testTargetName(t, proj2.PublicId)) + target1 := tcp.TestTarget(t, conn, proj.PublicId, tcp.TestTargetName(t, proj.PublicId)) + target2 := tcp.TestTarget(t, conn, proj2.PublicId, tcp.TestTargetName(t, proj2.PublicId)) - cp := target.Clone() - assert.True(!proto.Equal(cp.(*TcpTarget).TcpTarget, target2.TcpTarget)) + cp := target1.Clone() + assert.True(!proto.Equal(cp.(*tcp.Target).Target, target2.Target)) }) } -func TestTcpTable_SetTableName(t *testing.T) { +func TestTable_SetTableName(t *testing.T) { t.Parallel() - defaultTableName := DefaultTcpTableName + defaultTableName := tcp.DefaultTableName tests := []struct { name string setNameTo string @@ -353,30 +355,29 @@ func TestTcpTable_SetTableName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - def := allocTcpTarget() + def, _ := tcp.New("testScope") require.Equal(defaultTableName, def.TableName()) - s := allocTcpTarget() + s, _ := tcp.New("testScope") s.SetTableName(tt.setNameTo) assert.Equal(tt.want, s.TableName()) }) } } -func TestTcpTarget_oplog(t *testing.T) { - id := testId(t) +func TestTarget_oplog(t *testing.T) { + id := tcp.TestId(t) tests := []struct { name string - target *TcpTarget + target *tcp.Target op oplog.OpType want oplog.Metadata }{ { name: "simple", - target: func() *TcpTarget { - t := allocTcpTarget() + target: func() *tcp.Target { + t, _ := tcp.New(id) t.PublicId = id - t.ScopeId = id - return &t + return t }(), op: oplog.OpType_OP_TYPE_CREATE, want: oplog.Metadata{ @@ -390,7 +391,7 @@ func TestTcpTarget_oplog(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - got := tt.target.oplog(tt.op) + got := tt.target.Oplog(tt.op) assert.Equal(got, tt.want) }) } diff --git a/internal/target/tcp/testing.go b/internal/target/tcp/testing.go new file mode 100644 index 000000000..f78f61e9b --- /dev/null +++ b/internal/target/tcp/testing.go @@ -0,0 +1,62 @@ +package tcp + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/go-uuid" + "github.com/stretchr/testify/require" +) + +// TestTarget is used to create a Target that can be used by tests in other packages. +func TestTarget(t *testing.T, conn *db.DB, scopeId, name string, opt ...target.Option) *Target { + t.Helper() + opt = append(opt, target.WithName(name)) + opts := target.GetOpts(opt...) + require := require.New(t) + rw := db.New(conn) + tar, err := New(scopeId, opt...) + require.NoError(err) + id, err := db.NewPublicId(TargetPrefix) + require.NoError(err) + tar.PublicId = id + err = rw.Create(context.Background(), tar) + require.NoError(err) + + if len(opts.WithHostSources) > 0 { + newHostSets := make([]interface{}, 0, len(opts.WithHostSources)) + for _, s := range opts.WithHostSources { + hostSet, err := target.NewTargetHostSet(tar.PublicId, s) + require.NoError(err) + newHostSets = append(newHostSets, hostSet) + } + err := rw.CreateItems(context.Background(), newHostSets) + require.NoError(err) + } + if len(opts.WithCredentialSources) > 0 { + newCredLibs := make([]interface{}, 0, len(opts.WithCredentialSources)) + for _, cl := range opts.WithCredentialSources { + newCl, err := target.NewCredentialLibrary(tar.PublicId, cl) + require.NoError(err) + newCredLibs = append(newCredLibs, newCl) + } + err := rw.CreateItems(context.Background(), newCredLibs) + require.NoError(err) + } + return tar +} + +func testTargetName(t *testing.T, scopeId string) string { + t.Helper() + return fmt.Sprintf("%s-%s", scopeId, testId(t)) +} + +func testId(t *testing.T) string { + t.Helper() + id, err := uuid.GenerateUUID() + require.NoError(t, err) + return id +} diff --git a/internal/target/testing_test.go b/internal/target/tcp/testing_test.go similarity index 58% rename from internal/target/testing_test.go rename to internal/target/tcp/testing_test.go index 1d8c53863..7faeb2c14 100644 --- a/internal/target/testing_test.go +++ b/internal/target/tcp/testing_test.go @@ -1,4 +1,4 @@ -package target +package tcp_test import ( "context" @@ -8,6 +8,9 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -15,22 +18,27 @@ import ( func Test_TestTcpTarget(t *testing.T) { require := require.New(t) conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) wrapper := db.TestWrapper(t) - _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + repo, err := target.NewRepository(rw, rw, testKms) + require.NoError(err) + cats := static.TestCatalogs(t, conn, proj.PublicId, 1) hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 2) var sets []string for _, s := range hsets { sets = append(sets, s.PublicId) } - name := testTargetName(t, proj.PublicId) - target := TestTcpTarget(t, conn, proj.PublicId, name, WithHostSources(sets)) + name := tcp.TestTargetName(t, proj.PublicId) + tar := tcp.TestTarget(t, conn, proj.PublicId, name, target.WithHostSources(sets)) require.NotNil(t) - require.NotEmpty(target.PublicId) - require.Equal(name, target.Name) + require.NotEmpty(tar.PublicId) + require.Equal(name, tar.Name) - rw := db.New(conn) - foundSources, err := fetchHostSources(context.Background(), rw, target.PublicId) + _, foundSources, _, err := repo.LookupTarget(context.Background(), tar.PublicId) require.NoError(err) foundIds := make([]string, 0, len(foundSources)) for _, s := range foundSources { @@ -42,25 +50,29 @@ func Test_TestTcpTarget(t *testing.T) { func Test_TestCredentialLibrary(t *testing.T) { assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) wrapper := db.TestWrapper(t) - _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + repo, err := target.NewRepository(rw, rw, testKms) + require.NoError(err) - target := TestTcpTarget(t, conn, proj.PublicId, t.Name()) + tar := tcp.TestTarget(t, conn, proj.PublicId, t.Name()) store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] vlibs := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) var libIds []string - var libs []*CredentialLibrary + var libs []*target.CredentialLibrary for _, v := range vlibs { libIds = append(libIds, v.GetPublicId()) - lib := TestCredentialLibrary(t, conn, target.GetPublicId(), v.GetPublicId()) + lib := target.TestCredentialLibrary(t, conn, tar.GetPublicId(), v.GetPublicId()) require.NotNil(lib) libs = append(libs, lib) } assert.Len(libs, 2) - rw := db.New(conn) - foundSources, err := fetchCredentialSources(context.Background(), rw, target.PublicId) + _, _, foundSources, err := repo.LookupTarget(context.Background(), tar.PublicId) require.NoError(err) foundIds := make([]string, 0, len(foundSources)) for _, s := range foundSources { diff --git a/internal/target/tcp_target.go b/internal/target/tcp_target.go deleted file mode 100644 index b43d94aa3..000000000 --- a/internal/target/tcp_target.go +++ /dev/null @@ -1,110 +0,0 @@ -package target - -import ( - "context" - - "github.com/hashicorp/boundary/internal/db" - "github.com/hashicorp/boundary/internal/errors" - "github.com/hashicorp/boundary/internal/oplog" - "github.com/hashicorp/boundary/internal/target/store" - "google.golang.org/protobuf/proto" -) - -const ( - DefaultTcpTableName = "target_tcp" -) - -type TcpTarget struct { - *store.TcpTarget - tableName string `gorm:"-"` -} - -var ( - _ Target = (*TcpTarget)(nil) - _ db.VetForWriter = (*TcpTarget)(nil) - _ oplog.ReplayableMessage = (*TcpTarget)(nil) -) - -// NewTcpTarget creates a new in memory tcp target. WithName, WithDescription and -// WithDefaultPort options are supported -func NewTcpTarget(scopeId string, opt ...Option) (*TcpTarget, error) { - const op = "target.NewTcpTarget" - opts := getOpts(opt...) - if scopeId == "" { - return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing scope id") - } - t := &TcpTarget{ - TcpTarget: &store.TcpTarget{ - ScopeId: scopeId, - Name: opts.withName, - Description: opts.withDescription, - DefaultPort: opts.withDefaultPort, - SessionConnectionLimit: opts.withSessionConnectionLimit, - SessionMaxSeconds: opts.withSessionMaxSeconds, - WorkerFilter: opts.withWorkerFilter, - }, - } - return t, nil -} - -// allocTcpTarget will allocate a tcp target -func allocTcpTarget() TcpTarget { - return TcpTarget{ - TcpTarget: &store.TcpTarget{}, - } -} - -// Clone creates a clone of the TcpTarget -func (t *TcpTarget) Clone() interface{} { - cp := proto.Clone(t.TcpTarget) - return &TcpTarget{ - TcpTarget: cp.(*store.TcpTarget), - } -} - -// VetForWrite implements db.VetForWrite() interface and validates the tcp target -// before it's written. -func (t *TcpTarget) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error { - const op = "target.(TcpTarget).VetForWrite" - if t.PublicId == "" { - return errors.New(ctx, errors.InvalidParameter, op, "missing public id") - } - if opType == db.CreateOp { - if t.ScopeId == "" { - return errors.New(ctx, errors.InvalidParameter, op, "missing scope id") - } - if t.Name == "" { - return errors.New(ctx, errors.InvalidParameter, op, "missing name") - } - } - return nil -} - -// TableName returns the tablename to override the default gorm table name -func (t *TcpTarget) TableName() string { - if t.tableName != "" { - return t.tableName - } - return DefaultTcpTableName -} - -// SetTableName sets the tablename and satisfies the ReplayableMessage -// interface. If the caller attempts to set the name to "" the name will be -// reset to the default name. -func (t *TcpTarget) SetTableName(n string) { - t.tableName = n -} - -func (t *TcpTarget) oplog(op oplog.OpType) oplog.Metadata { - metadata := oplog.Metadata{ - "resource-public-id": []string{t.PublicId}, - "resource-type": []string{"tcp target"}, - "op-type": []string{op.String()}, - "scope-id": []string{t.ScopeId}, - } - return metadata -} - -func (t TcpTarget) GetType() string { - return "tcp" -} diff --git a/internal/target/testing.go b/internal/target/testing.go index a1761a659..b436a7963 100644 --- a/internal/target/testing.go +++ b/internal/target/testing.go @@ -2,63 +2,12 @@ package target import ( "context" - "fmt" "testing" "github.com/hashicorp/boundary/internal/db" - "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" ) -func TestTcpTarget(t *testing.T, conn *db.DB, scopeId, name string, opt ...Option) *TcpTarget { - t.Helper() - opt = append(opt, WithName(name)) - opts := getOpts(opt...) - require := require.New(t) - rw := db.New(conn) - target, err := NewTcpTarget(scopeId, opt...) - require.NoError(err) - id, err := newTcpTargetId() - require.NoError(err) - target.PublicId = id - err = rw.Create(context.Background(), target) - require.NoError(err) - - if len(opts.withHostSources) > 0 { - newHostSets := make([]interface{}, 0, len(opts.withHostSources)) - for _, s := range opts.withHostSources { - hostSet, err := NewTargetHostSet(target.PublicId, s) - require.NoError(err) - newHostSets = append(newHostSets, hostSet) - } - err := rw.CreateItems(context.Background(), newHostSets) - require.NoError(err) - } - if len(opts.withCredentialSources) > 0 { - newCredLibs := make([]interface{}, 0, len(opts.withCredentialSources)) - for _, cl := range opts.withCredentialSources { - newCl, err := NewCredentialLibrary(target.PublicId, cl) - require.NoError(err) - newCredLibs = append(newCredLibs, newCl) - } - err := rw.CreateItems(context.Background(), newCredLibs) - require.NoError(err) - } - return target -} - -func testTargetName(t *testing.T, scopeId string) string { - t.Helper() - return fmt.Sprintf("%s-%s", scopeId, testId(t)) -} - -func testId(t *testing.T) string { - t.Helper() - id, err := uuid.GenerateUUID() - require.NoError(t, err) - return id -} - // TestCredentialLibrary creates a CredentialLibrary for targetId and // libraryId. func TestCredentialLibrary(t *testing.T, conn *db.DB, targetId, libraryId string) *CredentialLibrary { diff --git a/internal/tests/api/targets/target_test.go b/internal/tests/api/targets/target_test.go index d625a2c6d..a5d901228 100644 --- a/internal/tests/api/targets/target_test.go +++ b/internal/tests/api/targets/target_test.go @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/servers/controller" - "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -250,7 +250,7 @@ func TestSet_Errors(t *testing.T) { assert.NotNil(apiErr) assert.Nil(tar) - _, err = tarClient.Read(tc.Context(), target.TcpTargetPrefix+"_doesntexis") + _, err = tarClient.Read(tc.Context(), tcp.TargetPrefix+"_doesntexis") require.Error(err) apiErr = api.AsServerError(err) assert.NotNil(apiErr) diff --git a/internal/tests/cli/boundary/_helpers.bash b/internal/tests/cli/boundary/_helpers.bash index ec87630ae..3b65f5de1 100644 --- a/internal/tests/cli/boundary/_helpers.bash +++ b/internal/tests/cli/boundary/_helpers.bash @@ -22,3 +22,11 @@ function strip_all() { echo "$1" | tr -d '"' | tr -d '\'\' } +function has_status_code() { + local json=$1 + local code=$2 + if [ echo "$json"|jq -c ".status_code == $code" ]; then + return 1 + fi + +} \ No newline at end of file diff --git a/internal/tests/cli/boundary/groups.bats b/internal/tests/cli/boundary/groups.bats index 498b95da3..798dc1719 100644 --- a/internal/tests/cli/boundary/groups.bats +++ b/internal/tests/cli/boundary/groups.bats @@ -59,6 +59,7 @@ export NEW_GROUP='test' local gid=$(group_id $NEW_GROUP) run delete_group $gid echo "$output" + run has_status_code "$output" "204" [ "$status" -eq 0 ] } diff --git a/internal/tests/cli/boundary/host_catalogs.bats b/internal/tests/cli/boundary/host_catalogs.bats index 3e3630429..376b0afe4 100644 --- a/internal/tests/cli/boundary/host_catalogs.bats +++ b/internal/tests/cli/boundary/host_catalogs.bats @@ -44,6 +44,7 @@ export NEW_HOST_CATALOG='test' local hid=$(host_catalog_id $NEW_HOST_CATALOG $DEFAULT_P_ID) run delete_host_catalog $hid echo "$output" + run has_status_code "$output" "204" [ "$status" -eq 0 ] } diff --git a/internal/tests/cli/boundary/host_sets.bats b/internal/tests/cli/boundary/host_sets.bats index 4f1a17667..bd095bfce 100644 --- a/internal/tests/cli/boundary/host_sets.bats +++ b/internal/tests/cli/boundary/host_sets.bats @@ -58,6 +58,7 @@ export NEW_HOST_SET='test' local hsid=$(host_set_id $NEW_HOST_SET $DEFAULT_HOST_CATALOG) run delete_host_set $hsid echo "$output" + run has_status_code "$output" "204" [ "$status" -eq 0 ] } diff --git a/internal/tests/cli/boundary/hosts.bats b/internal/tests/cli/boundary/hosts.bats index f25e15880..91ae9ef3e 100644 --- a/internal/tests/cli/boundary/hosts.bats +++ b/internal/tests/cli/boundary/hosts.bats @@ -44,6 +44,7 @@ export NEW_HOST='test' local hid=$(host_id $NEW_HOST $DEFAULT_HOST_CATALOG) run delete_host $hid echo "$output" + run has_status_code "$output" "204" [ "$status" -eq 0 ] } diff --git a/internal/tests/cli/boundary/roles.bats b/internal/tests/cli/boundary/roles.bats index 6546f9eff..3555c0011 100644 --- a/internal/tests/cli/boundary/roles.bats +++ b/internal/tests/cli/boundary/roles.bats @@ -102,6 +102,7 @@ export NEW_GRANT='id=*;type=*;actions=create,read,update,delete,list' local rid=$(role_id $NEW_ROLE $DEFAULT_GLOBAL) run delete_role $rid echo "$output" + run has_status_code "$output" "204" [ "$status" -eq 0 ] } diff --git a/internal/tests/cli/boundary/scopes.bats b/internal/tests/cli/boundary/scopes.bats index af7ece028..6d27495a3 100644 --- a/internal/tests/cli/boundary/scopes.bats +++ b/internal/tests/cli/boundary/scopes.bats @@ -64,6 +64,7 @@ export NEW_PROJECT='test_project' local sid=$(scope_id $NEW_PROJECT $parent) run delete_scope $sid echo "$output" + run has_status_code "$output" "204" [ "$status" -eq 0 ] } @@ -79,6 +80,7 @@ export NEW_PROJECT='test_project' local sid=$(scope_id $NEW_ORG $DEFAULT_GLOBAL) run delete_scope $sid echo "$output" + run has_status_code "$output" "204" [ "$status" -eq 0 ] } diff --git a/internal/tests/cli/boundary/target.bats b/internal/tests/cli/boundary/target.bats index f53f2d4be..812ce0d9b 100644 --- a/internal/tests/cli/boundary/target.bats +++ b/internal/tests/cli/boundary/target.bats @@ -92,6 +92,7 @@ load _helpers @test "boundary/target: default user can delete target" { local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME) run delete_target $id + run has_status_code "$output" "204" [ "$status" -eq 0 ] } diff --git a/internal/tests/cli/boundary/user.bats b/internal/tests/cli/boundary/user.bats index 9451ed655..8fac3eb86 100644 --- a/internal/tests/cli/boundary/user.bats +++ b/internal/tests/cli/boundary/user.bats @@ -68,7 +68,9 @@ export NEW_USER='test' @test "boundary/user: can delete $NEW_USER user" { login $DEFAULT_LOGIN local uid=$(user_id $NEW_USER) - run delete_user $uid + run delete_user $uid + echo $output + run has_status_code "$output" "204" [ "$status" -eq 0 ] } @@ -82,4 +84,4 @@ export NEW_USER='test' local aid=$(account_id $NEW_USER) run delete_account $aid [ "$status" -eq 0 ] -} \ No newline at end of file +} diff --git a/internal/types/subtypes/registry.go b/internal/types/subtypes/registry.go index 27423ad7a..10462cbd5 100644 --- a/internal/types/subtypes/registry.go +++ b/internal/types/subtypes/registry.go @@ -4,6 +4,7 @@ package subtypes import ( "fmt" "strings" + "sync" "github.com/hashicorp/boundary/internal/errors" ) @@ -22,13 +23,14 @@ func (t Subtype) String() string { // Registry stores a collection of boundary resource subtypes along with their // prefixes and allows for translating prefixes back to registered subtypes. -// Registry is not thread safe. type Registry struct { subtypesPrefixes map[string]Subtype knownSubtypes map[Subtype]interface{} + + sync.RWMutex } -// New Registry creates a new boundary resource subtype registry. +// NewRegistry creates a new boundary resource subtype registry. func NewRegistry() *Registry { return &Registry{ subtypesPrefixes: make(map[string]Subtype), @@ -39,6 +41,9 @@ func NewRegistry() *Registry { // SubtypeFromType returns the Subtype from the provided string or if // no Subtype was registered with that string Unknown is returned. func (r *Registry) SubtypeFromType(t string) Subtype { + r.RLock() + defer r.RUnlock() + st := Subtype(t) if _, ok := r.knownSubtypes[st]; !ok { return UnknownSubtype @@ -49,6 +54,9 @@ func (r *Registry) SubtypeFromType(t string) Subtype { // SubtypeFromId returns the Subtype from the provided id if the id's prefix // was registered with a Subtype. Otherwise Unknown is returned. func (r *Registry) SubtypeFromId(id string) Subtype { + r.RLock() + defer r.RUnlock() + i := strings.Index(id, "_") if i == -1 { return UnknownSubtype @@ -66,6 +74,9 @@ func (r *Registry) SubtypeFromId(id string) Subtype { // an error if the subtype has already been registered or if any of the // prefixes are associated with another subtype. func (r *Registry) Register(subtype Subtype, prefixes ...string) error { + r.Lock() + defer r.Unlock() + const op = "subtypes.(Registry).Register" if _, present := r.knownSubtypes[subtype]; present { return errors.NewDeprecated(errors.SubtypeAlreadyRegistered, op, fmt.Sprintf("subtype %q already registered", subtype)) diff --git a/scripts/trigger-merge-to-ent b/scripts/trigger-merge-to-ent new file mode 100755 index 000000000..1a6fb7a09 --- /dev/null +++ b/scripts/trigger-merge-to-ent @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [[ -z "${CIRCLE_REPOSITORY_URL}" ]]; then + exit 0 +fi + +if [[ "${CIRCLE_REPOSITORY_URL}" == *"boundary-enterprise"* ]]; then + exit 0 +fi + +if [[ -z ${CIRCLE_CI_ENT_TOK} ]]; then + exit 0 +fi + +curl -u ${CIRCLE_CI_ENT_TOK}: \ + -d 'build_parameters[CIRCLE_JOB]=merge-oss-main' \ + https://circleci.com/api/v1.1/project/github/hashicorp/boundary-enterprise/tree/main diff --git a/sdk/pbs/controller/api/resources/targets/target.pb.go b/sdk/pbs/controller/api/resources/targets/target.pb.go index b099979fb..cf0c3a283 100644 --- a/sdk/pbs/controller/api/resources/targets/target.pb.go +++ b/sdk/pbs/controller/api/resources/targets/target.pb.go @@ -461,11 +461,11 @@ type Target struct { Version uint32 `protobuf:"varint,80,opt,name=version,proto3" json:"version,omitempty"` // The type of the Target. Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty"` - // The IDs of the Host Sets associated with this Target. + // Output only. The IDs of the Host Sets associated with this Target. HostSetIds []string `protobuf:"bytes,100,rep,name=host_set_ids,proto3" json:"host_set_ids,omitempty"` // Output only. The Host Sets associated with this Target. HostSets []*HostSet `protobuf:"bytes,110,rep,name=host_sets,proto3" json:"host_sets,omitempty"` - // The IDs of the Host Sources associated with this Target. + // Output only. The IDs of the Host Sources associated with this Target. HostSourceIds []string `protobuf:"bytes,420,rep,name=host_source_ids,proto3" json:"host_source_ids,omitempty"` // Output only. The Host Sources associated with this Target. HostSources []*HostSource `protobuf:"bytes,430,rep,name=host_sources,proto3" json:"host_sources,omitempty"` @@ -475,7 +475,7 @@ type Target struct { SessionConnectionLimit *wrapperspb.Int32Value `protobuf:"bytes,130,opt,name=session_connection_limit,proto3" json:"session_connection_limit,omitempty"` // Optional boolean expression to filter the workers that are allowed to satisfy this request. WorkerFilter *wrapperspb.StringValue `protobuf:"bytes,140,opt,name=worker_filter,proto3" json:"worker_filter,omitempty"` - // The IDs of the application credential library ids associated with this Target. Deprecated: use application_credential_source_ids instead. + // Output only. The IDs of the application credential library ids associated with this Target. Deprecated: use application_credential_source_ids instead. // // Deprecated: Do not use. ApplicationCredentialLibraryIds []string `protobuf:"bytes,150,rep,name=application_credential_library_ids,proto3" json:"application_credential_library_ids,omitempty"` @@ -483,7 +483,7 @@ type Target struct { // // Deprecated: Do not use. ApplicationCredentialLibraries []*CredentialLibrary `protobuf:"bytes,180,rep,name=application_credential_libraries,proto3" json:"application_credential_libraries,omitempty"` - // The IDs of the application credential source ids associated with this Target. + // Output only. The IDs of the application credential source ids associated with this Target. ApplicationCredentialSourceIds []string `protobuf:"bytes,400,rep,name=application_credential_source_ids,proto3" json:"application_credential_source_ids,omitempty"` // Output only. The application credential sources associated with this Target. ApplicationCredentialSources []*CredentialSource `protobuf:"bytes,410,rep,name=application_credential_sources,proto3" json:"application_credential_sources,omitempty"` diff --git a/testing/dbtest/docker/postgresql.conf b/testing/dbtest/docker/postgresql.conf index 2cfab2543..675082eee 100644 --- a/testing/dbtest/docker/postgresql.conf +++ b/testing/dbtest/docker/postgresql.conf @@ -1,9 +1,10 @@ listen_addresses = '*' # listen on all addresses so other containers can connect if need be. -max_connections = 200000 # set max connections to the limit to ensure tests can always get a connection. - # Note that this may need to be decreased if <4G are available for the container. -shared_buffers = 256MB # A more reasonable default compared to the 32MB default config. +max_connections = 1000 # set max connections a higher limit than default; this will be set to the max limit for CI runs + # the limit to ensure tests can always get a connection. +shared_buffers = 64MB # A more reasonable default compared to the 32MB default config. # This gives postgres more memory for storing results. But given that short lived nature of the # test databases and the relatively small data sets, there should not be a need to increase it much more then this. + # For CI runs this is increased to 256MB. dynamic_shared_memory_type = posix # Use POSIX shared memory allocated using shm_open fsync = off # Do not flush, can cause data corruption, but since it is for tests and the data is dropped anyway... synchronous_commit = off # Do not wait for WAL records to be written to disk before reporting success diff --git a/website/components/openapi-page/index.jsx b/website/components/openapi-page/index.jsx deleted file mode 100644 index a3853708b..000000000 --- a/website/components/openapi-page/index.jsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useState, useRef } from 'react' -import Link from 'next/link' -import OperationObject from './partials/operation-object' -import HashiHead from '@hashicorp/react-head' -import DocsSidenav from '@hashicorp/react-docs-sidenav' -import Content from '@hashicorp/react-content' -import { getPathsFromSchema, getPropsForPage } from './utils/routing-utils' -import styles from './style.module.css' -import useOnClickOutside from 'lib/hooks/use-on-click-outside' - -function OpenApiPage({ - info, - operationCategory, - navData, - productName, - productSlug, - currentPath, -}) { - const operationsRef = useRef(null) - const [expandedOperations, setExpandedOperations] = useState([]) - useOnClickOutside(operationsRef, () => setExpandedOperations([])) - - function setOperationState(slug, isExpanded) { - const newStates = expandedOperations.filter((s) => s !== slug) - if (isExpanded) newStates.push(slug) - setExpandedOperations(newStates) - } - - const pageTitle = operationCategory ? operationCategory.name : info.title - - return ( -
- - - -

- {info.title} -

-

- {operationCategory.name} -

-
- {operationCategory.operations.map((op) => { - const isExpanded = - expandedOperations.indexOf(op.operationId) !== -1 - - return ( - - setOperationState(op.operationId, !isCollapsed) - } - /> - ) - })} -
- - ) : ( - <> -

- {info.title} -

-

- Select a service from the sidebar. -

- - ) - } - /> -
- ) -} - -export { getPathsFromSchema, getPropsForPage } -export default OpenApiPage diff --git a/website/components/openapi-page/index.props.js b/website/components/openapi-page/index.props.js deleted file mode 100644 index 92c651d83..000000000 --- a/website/components/openapi-page/index.props.js +++ /dev/null @@ -1,32 +0,0 @@ -import PropTypes from 'prop-types' -import OpenAPIPage from '.' - -function OpenAPIPageProps(props) { - return -} - -OpenAPIPageProps.propTypes = { - /** Information about the API, pulled from the schema's [Info Object](https://swagger.io/specification/v2/#info-object) */ - info: PropTypes.shape({ - title: PropTypes.string.isRequired, - description: PropTypes.string, - version: PropTypes.string, - }), - /** The name of the associated HashiCopr product, used in page metadata */ - productName: PropTypes.string, - /** The slug of the associated HashiCorp product, used for theming. Only supports "boundary" or a default theme at present.*/ - productSlug: PropTypes.oneOf(['boundary']), - /** An object that determines which operations will be shown on the page */ - operationCategory: PropTypes.shape({ - /** The name of the operation "category", used as the page title */ - name: PropTypes.string, - /** The slug-ified name, used as the page's location */ - slug: PropTypes.string, - /** An array of [Operation Objects](https://swagger.io/specification/v2/#operation-object) to be displayed on the page */ - operations: PropTypes.arrayOf(PropTypes.object), - }), - /** ["order"](https://github.com/hashicorp/react-components/blob/11d7bda7d518bc675ab0033067d43057e21f3b77/packages/docs-sidenav/index.js#L9) data generated to be passed directly to our DocsSidenav component */ - sidenavOrderData: PropTypes.object, -} - -export default OpenAPIPageProps diff --git a/website/components/openapi-page/partials/chevron-down/black.jsx b/website/components/openapi-page/partials/chevron-down/black.jsx deleted file mode 100644 index 2c7c0fa0c..000000000 --- a/website/components/openapi-page/partials/chevron-down/black.jsx +++ /dev/null @@ -1,23 +0,0 @@ -function ChevronDownBlack({ title, ...props }) { - return ( - - {title ? {title} : null} - - - ) -} - -export default ChevronDownBlack diff --git a/website/components/openapi-page/partials/collapsible/collapsible.module.css b/website/components/openapi-page/partials/collapsible/collapsible.module.css deleted file mode 100644 index a2ad211a2..000000000 --- a/website/components/openapi-page/partials/collapsible/collapsible.module.css +++ /dev/null @@ -1,18 +0,0 @@ -.outer { - overflow: hidden; - height: 0; - &[data-useanimation='true'] { - transition: height 0.4s; - } -} - -.inner { - opacity: 1; - - &[data-iscollapsed='true'] { - opacity: 0; - } - &[data-useanimation='true'] { - transition: opacity 0.4s; - } -} diff --git a/website/components/openapi-page/partials/collapsible/index.js b/website/components/openapi-page/partials/collapsible/index.js deleted file mode 100644 index 673d8111d..000000000 --- a/website/components/openapi-page/partials/collapsible/index.js +++ /dev/null @@ -1,81 +0,0 @@ -import { Component } from 'react' -import styles from './collapsible.module.css' - -/* - Collapsible content block, receives children and a 'isCollapsed' prop, - smoothly animates from collapsed (height 0) to expanded (height auto) -*/ -export default class Collapsible extends Component { - constructor(props) { - super(props) - this.animateHeight = this.animateHeight.bind(this) - } - - componentDidMount() { - this.animateHeight() - } - - componentDidUpdate(prevProps) { - // Only update if isCollapsed prop has changed - if (this.props.isCollapsed === prevProps.isCollapsed) return - this.animateHeight() - } - - animateHeight() { - function handleTransitionEnd(event) { - const elem = event.target - const elemStyle = getComputedStyle(elem) - const isCollapsed = elem.getAttribute('data-iscollapsed') - if (event.propertyName !== 'height') return - const innerElem = event.target.firstChild - const innerElemStyle = getComputedStyle(innerElem) - if (isCollapsed === 'false') { - elem.style.height = 'auto' - innerElem.style.transitionDelay = '0s' - } else if (isCollapsed === 'true') { - const heightDurn = parseFloat(elemStyle['transition-duration']) - const opactiyDurn = parseFloat(innerElemStyle['transition-duration']) - innerElem.style.transitionDelay = `${heightDurn - opactiyDurn}s` - } - elem.removeEventListener('transitionend', handleTransitionEnd, false) - } - const { isCollapsed } = this.props - // Do not attempt update if container elem is invalid - const elem = this.containerRef - if (!elem) return - const elemStyle = getComputedStyle(elem) - if (isCollapsed) { - // Transition from auto to 0 - elem.style.height = elemStyle.height - elem.offsetHeight // force repaint - elem.style.height = '0px' - elem.setAttribute('data-iscollapsed', 'true') - elem.addEventListener('transitionend', handleTransitionEnd, false) - } else { - // Transition from 0 to auto - const prevHeight = elem.style.height - elem.style.height = 'auto' - const endHeight = elemStyle.height - elem.style.height = prevHeight - elem.offsetHeight // force repaint - elem.style.height = endHeight - elem.setAttribute('data-iscollapsed', 'false') - elem.addEventListener('transitionend', handleTransitionEnd, false) - } - } - - render() { - const { children, noAnimation } = this.props - return ( -
(this.containerRef = ref)} - data-useanimation={Boolean(!noAnimation)} - > -
- {children} -
-
- ) - } -} diff --git a/website/components/openapi-page/partials/operation-object/index.js b/website/components/openapi-page/partials/operation-object/index.js deleted file mode 100644 index 2c6a87373..000000000 --- a/website/components/openapi-page/partials/operation-object/index.js +++ /dev/null @@ -1,154 +0,0 @@ -import Collapsible from '../collapsible' -import ResponseObject from '../response-object' -import PropertyObject from '../property-object' -import { capitalCase } from 'change-case' -import useHover from '../../../../lib/hooks/use-hover' -import SvgrChevronDown from '../chevron-down/black' - -import styles from './operation-object.module.css' - -function OperationObject({ - data, - path, - type, - isHighlighted, - isCollapsed, - setIsCollapsed, -}) { - const [headerRef, isHeaderHovered] = useHover() - - // const [isCollapsed, setIsCollapsed] = useState(true) - const { operationId, parameters, responses, summary } = data - const successResponse = responses['200'] - const title = capitalCase(operationId.split('_').slice(1).join()) - - // Group parameter properties by type - const pathParams = parameters.filter((p) => p.in === 'path') - const queryParams = parameters.filter((p) => p.in === 'query') - const bodyParam = parameters.filter((p) => p.in === 'body')[0] // Note: we only accept a single "in=body" param - const bodyProps = bodyParam ? getBodyParamProps(bodyParam) : [] - - return ( -
-
setIsCollapsed(!isCollapsed)} - > -
-
- {title} -
-
- - {type.toUpperCase()}{' '} - - {path} -
-
-
- - {isCollapsed ? 'Expand' : 'Collapse'} - - - - -
-
- -
-

{summary}

- -

Request

- {pathParams.length > 0 ? ( - - ) : null} - {queryParams.length > 0 ? ( - - ) : null} - {bodyProps.length > 0 ? ( - - ) : null} -
- } - columnTwo={ -
-

Response

- {!!successResponse ? ( -
-

- Successful Response -

- -
- ) : ( -

No response has been defined.

- )} -
- } - /> -
- - - ) -} - -function getBodyParamProps(bodyParam) { - // We always expect the bodyParam to be an object, - // with a schema which defines the body properties. - if (!bodyParam.schema || !bodyParam.schema.properties) return [] - // We flatten these properties to avoid showing a - // "collapsed object" UI under the "Body Parameters" section, - // which would be a bit redundant and annoying to have to expand - const bodyPropsObj = bodyParam.schema.properties - const bodyProps = Object.keys(bodyPropsObj).reduce((acc, key) => { - const data = Object.assign({}, bodyPropsObj[key]) - // We need the property name. This is usually be handled by "key" in an object, - // but we're flattening the object so we need to make sure it's there - data.name = key - if (!data.readOnly) acc.push(data) - return acc - }, []) - return bodyProps -} - -function TwoColumnLayout({ columnOne, columnTwo }) { - return ( -
-
{columnOne}
-
-
{columnTwo}
-
- ) -} - -function Parameters({ title, params }) { - return ( -
-

- {title} -

- {params.map((parameter, idx) => { - return ( - - ) - })} -
- ) -} - -export default OperationObject diff --git a/website/components/openapi-page/partials/operation-object/index.props.js b/website/components/openapi-page/partials/operation-object/index.props.js deleted file mode 100644 index a414821d5..000000000 --- a/website/components/openapi-page/partials/operation-object/index.props.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import OperationObject from '.' - -function OperationObjectProps(props) { - return -} - -/** Displays [Operation Object](https://swagger.io/specification/v2/#operation-object) data to the user. */ -OperationObjectProps.propTypes = { - /** The path to the endpoint where this operation is executed */ - path: PropTypes.string.isRequired, - /** The type of operation */ - type: PropTypes.oneOf([ - 'get', - 'put', - 'post', - 'delete', - 'options', - 'head', - 'patch', - ]).isRequired, - /** A subset of [Operation Object](https://swagger.io/specification/v2/#operation-object) relevant to the UI */ - data: PropTypes.shape({ - /** Flag whether this operation is deprecated */ - deprecated: PropTypes.bool, - /** A unique string used to identify the operation.*/ - operationId: PropTypes.string, - /** Array of parameter objects. We group them by their "in" property, and pass them to the component for rendering. */ - parameters: PropTypes.arrayOf( - PropTypes.shape({ - /** The name of the parameter. Parameter names are case sensitive. */ - name: PropTypes.string.isRequired, - /** A brief, plain text description of the parameter. Note that [GitHub-flavor markdown](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) is not currently supported. */ - description: PropTypes.string, - /** Indicates whether this parameter is mandatory. */ - required: PropTypes.boolean, - /** The location of the parameter. Note: `formData` and `header` are not currently supported in the UI. */ - in: PropTypes.oneOf(['query', 'path', 'body']).isRequired, - /** Schema is used for "body" parameters. */ - schema: PropTypes.object, - /** Type defines the parameter type, for non-"body" parameters. */ - type: PropTypes.oneOf([ - 'array', - 'boolean', - 'integer', - 'number', - 'object', - 'string', - ]), - }) - ), - /** Responses object, keys are HTTP response codes */ - responses: PropTypes.shape({ - /** We only support "200" response codes, for now. This object is passed to the component. */ - 200: PropTypes.object, - }), - }), -} - -export default OperationObjectProps diff --git a/website/components/openapi-page/partials/operation-object/operation-object.module.css b/website/components/openapi-page/partials/operation-object/operation-object.module.css deleted file mode 100644 index 93bea1984..000000000 --- a/website/components/openapi-page/partials/operation-object/operation-object.module.css +++ /dev/null @@ -1,145 +0,0 @@ -.root { - border-bottom: 1px solid var(--gray-5); - margin: 0; - margin-top: -1px; - position: relative; - transition: border-color 0.2s; - - &[data-is-hovered='true'] { - border-bottom-color: var(--brand); - z-index: 1; - } -} - -.header { - display: flex; - position: relative; - padding: 16px 0; - - &:hover { - cursor: pointer; - } -} - -.meta { - width: 100%; -} - -.title { - position: relative; - display: inline-block; - composes: g-type-body-x-strong from global; - color: var(--gray-2); - margin: 0 0 4px 0; - padding: 0; - - &[data-is-highlighted='true'] { - &::before { - content: '\200B'; - position: absolute; - top: -4px; - left: -8px; - right: -8px; - bottom: -4px; - background: var(--warning-l1); - z-index: -1; - border-radius: 8px; - } - } - - &[data-is-hovered='true'] { - color: var(--brand); - } -} - -.endpoint { - composes: g-type-code from global; - - & .method { - color: var(--gray-3); - - &[data-is-hovered='true'] { - color: var(--brand); - } - } - - & .path { - color: var(--gray-2); - } -} - -.toggleButton { - border: none; - color: var(--brand); - background: none; - display: flex; - align-items: center; - font-size: inherit; - margin: 0; - padding: 0 0 0 24px; - transition: color 0.2s; - - & .toggleIcon { - width: 16px; - height: 16px; - margin-left: 8px; - position: relative; - top: 1px; - transition: transform 0.3s; - display: flex; - align-items: center; - justify-content: center; - - & svg { - & [fill] { - fill: var(--brand); - } - & [stroke] { - stroke: var(--brand); - } - } - - &[data-is-collapsed='false'] { - transform: rotate(-180deg); - } - } - - &[data-is-hovered='true'] { - color: var(--brand); - } -} - -.details { - padding-bottom: 80px; - - & .summary { - color: var(--gray-2); - margin: 8px 0 24px 0; - } - - & .columnHeading { - margin: 0 0 1em 0; - font-weight: bold; - } - - & .columnSectionHeading { - margin: 0 !important; /* Needed to override ".g-content p" margins */ - padding-bottom: 8px; - border-bottom: 1px solid var(--gray-5); - color: var(--gray-3); - } -} - -.twoColumnLayout { - display: flex; - - & > div:nth-child(1), - & > div:nth-child(3) { - position: relative; - width: calc(50% - 16px); - } - - & > div:nth-child(2) { - width: 32px; - } -} diff --git a/website/components/openapi-page/partials/property-object/index.js b/website/components/openapi-page/partials/property-object/index.js deleted file mode 100644 index 63e672a20..000000000 --- a/website/components/openapi-page/partials/property-object/index.js +++ /dev/null @@ -1,91 +0,0 @@ -import { useState } from 'react' -import Collapsible from '../collapsible' -import SvgrChevronDown from '../chevron-down/black' -import styles from './property-object.module.css' - -function PropertyObject({ - name, - data, - isFirstItem, - isLastItem, - arrayDepth = 0, -}) { - const [isCollapsed, setIsCollapsed] = useState(true) - const isArray = data.type === 'array' - if (isArray) - return ( - - ) - const hasProperties = data.type === 'object' && Boolean(data.properties) - - const typeArraySuffix = - arrayDepth > 0 ? arrayFrom(arrayDepth, '[]').join('') : '' - const typeString = `${data.type}${typeArraySuffix}` - return ( -
- {name}{' '} - {typeString}{' '} - {data.required ? ( - - Required - - ) : null} - {data.title && ( -

{data.title}

- )} - {data.description && ( -

- {data.description} -

- )} - {hasProperties && ( -
- - -
- {Object.keys(data.properties).map((propertyKey, idx) => { - return ( - - ) - })} -
-
-
- )} -
- ) -} - -function arrayFrom(length, value = null) { - let array = [] - for (var i = 0; i < length; i++) { - array.push(value) - } - return array -} - -export default PropertyObject diff --git a/website/components/openapi-page/partials/property-object/property-object.module.css b/website/components/openapi-page/partials/property-object/property-object.module.css deleted file mode 100644 index c7f9a73f6..000000000 --- a/website/components/openapi-page/partials/property-object/property-object.module.css +++ /dev/null @@ -1,85 +0,0 @@ -.root { - border-top: 1px solid var(--gray-5); - border-bottom: 1px solid var(--gray-5); - padding: 16px 0; - margin: -1px 0 0 0; - - &[data-is-first-item='true'] { - border-top: none; - } - - &[data-is-last-item='true'] { - border-bottom: none; - } - - & .name { - color: var(--brand); - background: rgba(29, 30, 35, 0.07); - border-radius: 3px; - padding: 0.3em 0.625em; - } - - & .typeString { - color: var(--gray-2); - background: none; - padding: 0; - margin-left: 2px; - } - - & .requiredFlag { - color: var(--danger-d1); - margin-left: 4px; - } - - & .title, - & .description { - color: var(--gray-2); - margin: 8px 0 0 0; - } - - & .toggleButton { - border: 1px solid red; - border: none; - color: var(--brand); - background: none; - display: flex; - align-items: center; - margin: 8px 0 0 0; - padding: 0; - - & .toggleIcon { - position: relative; - top: 1px; - width: 16px; - height: 16px; - margin-right: 8px; - transition: transform 0.3s; - display: flex; - align-items: center; - justify-content: center; - - & svg { - & [fill] { - fill: var(--brand); - } - & [stroke] { - stroke: var(--brand); - } - } - - &[data-is-collapsed='false'] { - transform: rotate(-180deg); - } - } - - &:hover { - cursor: pointer; - } - } - - & .propertiesContainer { - margin: 8px 0 0 8px; - padding-left: 16px; - border-left: 1px solid var(--gray-5); - } -} diff --git a/website/components/openapi-page/partials/response-object/index.js b/website/components/openapi-page/partials/response-object/index.js deleted file mode 100644 index 4d39fe833..000000000 --- a/website/components/openapi-page/partials/response-object/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropertyObject from '../property-object' - -function ResponseObject({ data }) { - // `schema` can be empty, which means the response does not return content - // We currently only support object responses (ie those that have schema.properties) in the UI - // Ref: https://swagger.io/specification/v2/#response-object - if (!data || !data.schema || !data.schema.properties) { - return
No content.
- } - return ( -
- {Object.keys(data.schema.properties).map((propertyKey, idx) => { - return ( - - ) - })} -
- ) -} - -export default ResponseObject diff --git a/website/components/openapi-page/partials/response-object/index.props.js b/website/components/openapi-page/partials/response-object/index.props.js deleted file mode 100644 index 8ed469813..000000000 --- a/website/components/openapi-page/partials/response-object/index.props.js +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types' -import ResponseObject from '.' - -function ResponseObjectProps(props) { - return -} - -ResponseObjectProps.propTypes = { - /** [Response Object](https://swagger.io/specification/v2/#response-object) data. */ - data: PropTypes.shape({ - /** A [Schema Object](https://swagger.io/specification/v2/#schema-object) that describes the response. Note that we currently only support objects (with schema.properties) responses in the UI. */ - schema: PropTypes.object, - }), -} - -export default ResponseObjectProps diff --git a/website/components/openapi-page/style.module.css b/website/components/openapi-page/style.module.css deleted file mode 100644 index 52da68316..000000000 --- a/website/components/openapi-page/style.module.css +++ /dev/null @@ -1,57 +0,0 @@ -.root { - composes: g-grid-container from global; - display: flex; - flex-direction: column; - flex: 1 0 auto; - position: relative; - width: 100%; - - @media (min-width: 940px) { - flex-direction: row; - margin-bottom: 24px; - margin-top: 72px; - } - - /* currently only supports "boundary", may want to extend to support other product themes */ - &[data-theme='boundary'] { - --brand: var(--boundary); - } - - & .pageHeading { - margin: 0; - } - - & .categoryHeading { - margin: 32px 0 16px 0; - } - - & :global(.g-content) { - padding-left: 0; - padding-right: 0; - margin: 0; - - @media (max-width: 939px) { - padding-left: 0; - padding-right: 0; - margin-top: 64px; - } - - & > h1:first-child { - margin-top: 0; - } - } - - & :global(.g-section-header) { - margin-bottom: 100px; - } - - /* TODO: this should be applied in global styles, temporary override here */ - & pre, - & code { - font-size: 0.875em; - } - - & pre code { - font-size: 1em; - } -} diff --git a/website/components/openapi-page/utils/routing-utils.js b/website/components/openapi-page/utils/routing-utils.js deleted file mode 100644 index 0bffdaa88..000000000 --- a/website/components/openapi-page/utils/routing-utils.js +++ /dev/null @@ -1,121 +0,0 @@ -import { capitalCase } from 'change-case' -import slugify from '@hashicorp/remark-plugins/generate_slug' - -/* Gieven a schema, current service slug, and parentPath, return all props needed for an openapi component page */ -export function getPropsForPage(schema, params) { - // parse the data we'll show to the user from the schema - const operationObjects = getOperationObjects(schema) - const serviceIds = getServiceIds(operationObjects) - // info and sidenav data are needed on all pages - const info = schema.info - const navData = getSidenavData(serviceIds) - // If there's no "page" param, then this is the landing page - const isLanding = !params || !params.page || params.page.length == 0 - - const currentPath = params && params.page ? params.page.join('/') : '' - - // Otherwise, we should have an operationCategory that matches the slug-ified ID from the URL path - const operationCategory = isLanding - ? false - : serviceIds - .filter((id) => getServiceSlug(id) === params.page[0]) - .map((serviceId) => { - const name = capitalCase(serviceId) - const slug = getServiceSlug(serviceId) - const operations = operationObjects.filter( - (o) => getServiceId(o.operationId) === serviceId - ) - return { name, slug, operations } - })[0] - - return { info, navData, operationCategory, currentPath } -} - -/* Given a schema, return all the paths we'll render for our openapi generated docs */ -export function getPathsFromSchema(schema) { - // Assign each operation category to a URL using its slug-ified ID - const operationObjects = getOperationObjects(schema) - const slugs = getServiceIds(operationObjects).map(getServiceSlug) - // We need a path for each "service" - const paths = slugs.map((slug) => ({ params: { page: [slug] } })) - // We also push a path for an "/" index page - paths.push({ params: { page: [] } }) - return paths -} - -/* Given a list of service ids, return an object suitable to pass to -to render a flat list of services */ -function getSidenavData(serviceIds) { - const order = serviceIds.map((serviceId) => { - return { - title: capitalCase(serviceId), - indexData: true, - path: getServiceSlug(serviceId), - } - }) - return order -} - -/* Given a serviceId, which is typically PascalCase string, return a slugified kebab-case string (ie: pascal-case rather than pascalcase) */ -function getServiceSlug(serviceId) { - return slugify(capitalCase(serviceId)) -} - -/* Given an operationId, return the "serviceId" */ -function getServiceId(operationId) { - // We expect operationIds to have two parts, separated by an underscore - // The "serviceId" is the first part of the value - return operationId.split('_')[0] -} - -/* Given a schema, return an array of unique operation "category" strings */ -export function getServiceIds(operationObjects) { - const operationIdCategories = operationObjects - .map((o) => getServiceId(o.operationId)) - .sort() - // Several related operationIds may have the same "category" part, - // so we filter for unique values before returning - return filterUnique(operationIdCategories) -} - -/* Given an array of values, return an array without duplicate items */ -function filterUnique(array) { - return array.filter((value, idx) => array.indexOf(value) === idx) -} - -/* Given a schema, return a flattened list of operation objects */ -function getOperationObjects(schema) { - const pathItemObjects = Object.keys(schema.paths).reduce((acc, path) => { - acc.push({ __path: path, ...schema.paths[path] }) - return acc - }, []) - - const operationObjects = pathItemObjects.reduce((acc, pathItemObject) => { - // Each path can support many operations through different request types - const requestTypes = [ - 'get', - 'put', - 'post', - 'delete', - 'options', - 'head', - 'patch', - ] - const pathOperations = requestTypes.reduce((acc, type) => { - // Not all paths will support every request type - if (!pathItemObject[type]) return acc - // If the request type is supported, push the associated operation - acc.push({ - __type: type, - __path: pathItemObject.__path, - ...pathItemObject[type], - }) - return acc - }, []) - return acc.concat(pathOperations) - }, []) - - return operationObjects -} - -export default getPathsFromSchema diff --git a/website/content/docs/installing/index.mdx b/website/content/docs/installing/index.mdx index 8073baef3..9474e701f 100644 --- a/website/content/docs/installing/index.mdx +++ b/website/content/docs/installing/index.mdx @@ -7,4 +7,6 @@ description: |- # Overview -This section covers installing Boundary for different modes of operation and across different environments. +This section covers installing Boundary for different modes of operation and across different environments + +For community-supported reference architectures, please see [Reference Architectures](/docs/installing/reference-architectures). diff --git a/website/content/docs/installing/reference-architectures.mdx b/website/content/docs/installing/reference-architectures.mdx new file mode 100644 index 000000000..2742af426 --- /dev/null +++ b/website/content/docs/installing/reference-architectures.mdx @@ -0,0 +1,7 @@ +# Reference Architectures +A number of community-supported reference architectures for non-dev deployments can be found in Boundary's dedicated [reference architecture repo](https://github.com/hashicorp/boundary-reference-architecture). + + +While some of the initial reference deployments have been seeded by the Boundary team, the seeded examples are for demonstration purposes only and intended convey how to get Boundary up and running on popular cloud and container platforms. + +For those exploring Boundary in non-dev mode, we highly encourage contributing configurations to the repo such that others may benefit from them. diff --git a/website/content/docs/what-is-boundary.mdx b/website/content/docs/what-is-boundary.mdx new file mode 100644 index 000000000..c587b18bd --- /dev/null +++ b/website/content/docs/what-is-boundary.mdx @@ -0,0 +1,54 @@ +--- +layout: docs +page_title: What is Boundary? +description: And introduction to Boundary +--- + +# What is Boundary? + +Welcome to Boundary! This introduction section covers what Boundary is, the problem Boundary intends to solve, and how Boundary compares to other software. If you want to dive into using Boundary, head over to the [Getting Started](/docs/getting-started) section. + +Boundary is an intelligent proxy that creates granular, identity-based access controls for dynamic infrastructure. Boundary’s workflow layers security controls and integrations at multiple levels to monitor and manage user access through: + +- Tightly scoped identity-based permissions +- Just-in-time network and credential access for sessions via [HashiCorp Vault](https://vaultproject.io/) +- Single sign-on to target services and applications via external identity providers +- Access-as-code to automate the configuration of user permissions +- Automated discovery of target systems +- Session monitoring and management for access created via Boundary. + +## Boundary Goals + +**Zero Trust Security**: The foundation of Boundary is an identity-based, “Zero-Trust”, access model. Zero-Trust means user access is continuously authenticated and only authorized when rules and policies tied to the user’s identity are verified. + +**A Consistent Workflow for Access**: Boundary connects the user securely to their infrastructure regardless of cloud platform, target environment, or identity provider. Boundary creates a consistent workflow for user authentication and authorization to create secure, ephemeral sessions that can be monitored and managed by administrators. + +![Boundary workflow](/img/boundary.png) + +**Extensibility with the Ecosystem**: Modern organizations have a multilayered matrix of identity providers, policy engines, secrets management tools, target types, and cloud providers that must integrate and reside within the access workflow. Boundary's extensible ecosystem supports the user's vendor-of-choice in this access workflow. + +## Why Boundary? + +With the many varying infrastructure services and tooling used in increasingly dynamic environments, organizations must have secure access to all targets within and beyond their perimeter. + +Boundary provides a simple way for verified users to have secure access to cloud and self-managed infrastructures without exposing your network or the use of managing credentials. Boundary is an open source workflow that enables just-in-time, role-based access for dynamic infrastructure. + +## Boundary Use-Cases + +### Zero Trust access + +Boundary’s access-on-demand workflow securely connects trusted identities to infrastructure services based on granular, admin-defined permission grants. Boundary removes the need to create or store credentials when accessing services. In this way, Boundary can be used to extend or replace many traditional access solutions like VPNs. + +Traditional access solutions do not employ the the Zero-Trust philosophy - meaning they fail to authenticate and authorize users’ access and actions continuously, and often lack granular access controls. + +### Multi-Cloud Access + +Having different access workflows for various infrastructure and identity providers doesn’t scale well for administrators or users. Multi-cloud organizations have a growing need to standardize access through a single workflow. Boundary creates a centralized layer of identity-based authentication and authorization to manage access to the infrastructure, regardless of the platform on which it resides. + +### Single Sign-on with Integrated Secrets Management + +Boundary enables a single sign-on access model with authentication from trusted identity providers, such as Azure Active Directory, Auth0, and Okta. Once authenticated, users may create sessions with integrated credential management from HashiCorp Vault without the need to re-authenticate. + +### Session Monitoring + +Boundary provides session monitoring capabilities that give security administrators visibility into user access. Sessions are logged and consumable via the Boundary administrator UI as well as business intelligence (BI) and security information and event management (SIEM) tools. diff --git a/website/data/alert-banner.js b/website/data/alert-banner.js index 2fe3f3bad..48fc08984 100644 --- a/website/data/alert-banner.js +++ b/website/data/alert-banner.js @@ -2,12 +2,11 @@ export const ALERT_BANNER_ACTIVE = true // https://github.com/hashicorp/web-components/tree/master/packages/alert-banner export default { - tag: 'Oct 19-21', - url: - 'https://hashiconf.com/global/?utm_campaign=22Q3_WW_HASHICONFGLOBAL_EVENT-USER&utm_source=CorpBanner&utm_medium=EVT&utm_offer=EVENT-USER', - text: 'The countdown to HashiConf Global is on. View the full schedule now.', - linkText: 'View Schedule', - // Set the expirationDate prop with a datetime string (e.g. '2020-01-31T12:00:00-07:00') - // if you'd like the component to stop showing at or after a certain date - expirationDate: '2021-10-19T12:00:00-07:00', + tag: 'Oct 19-21', + url: 'https://hashiconf.com/global/?utm_source=DocsBanner', + text: 'HashiConf Global is live.', + linkText: 'Join Now', + // Set the expirationDate prop with a datetime string (e.g. '2020-01-31T12:00:00-07:00') + // if you'd like the component to stop showing at or after a certain date + expirationDate: '2021-10-20T23:00:00-07:00', } diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index f7164abb7..4a0d6fc64 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -1,4 +1,8 @@ [ + { + "title": "What is Boundary?", + "path": "what-is-boundary" + }, { "title": "Getting Started", "routes": [ @@ -52,6 +56,10 @@ { "title": "High Availability Install", "path": "installing/high-availability" + }, + { + "title": "Reference Architectures", + "path": "installing/reference-architectures" } ] }, diff --git a/website/package-lock.json b/website/package-lock.json index a085e664e..0b24b5899 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -11,6 +11,7 @@ "@apidevtools/json-schema-ref-parser": "9.0.9", "@hashicorp/mktg-global-styles": "^4.0.0", "@hashicorp/mktg-logos": "^1.2.0", + "@hashicorp/platform-analytics": "^0.2.0", "@hashicorp/platform-code-highlighting": "^0.1.2", "@hashicorp/platform-runtime-error-monitoring": "^0.1.0", "@hashicorp/platform-util": "^0.1.0", @@ -25,6 +26,7 @@ "@hashicorp/react-hero": "^8.0.2", "@hashicorp/react-image": "^4.0.3", "@hashicorp/react-markdown-page": "^1.4.3", + "@hashicorp/react-open-api-page": "^2.0.1", "@hashicorp/react-product-downloads-page": "^2.5.2", "@hashicorp/react-product-features-list": "^5.0.0", "@hashicorp/react-search": "^6.1.1", @@ -59,6 +61,18 @@ "npm": ">=7.0.0" } }, + "@hashicorp/platform-analytics": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@hashicorp/platform-analytics/-/platform-analytics-0.1.0.tgz", + "integrity": "sha512-kR/E0KWemjazSrSFN9l22v8JqlQvkgjgkZHPJEhGiBt05LMtt2ugxIakv+gnzW5lU9434nnr7oDooUyj4eufPA==", + "extraneous": true + }, + "fathom-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fathom-client/-/fathom-client-3.2.0.tgz", + "integrity": "sha512-WF/qA5wXYSuA5K8uiIhGNbErOcTAmfLEWrBxWP2px2dEc9waH9zxjdh9k0F907VCBhdJrv+f7V3HT/Pmro40zA==", + "extraneous": true + }, "node_modules/@algolia/cache-browser-local-storage": { "version": "4.10.5", "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.5.tgz", @@ -864,6 +878,11 @@ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" }, + "node_modules/@hashicorp/flight-icons": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hashicorp/flight-icons/-/flight-icons-1.2.0.tgz", + "integrity": "sha512-gMsPGbDXSBWe4v7sFjG+ZlQdc8ipNLi5w98muaKU5tUNG027b5XNy5chkiT1/gsygi5ZlKXU0klgC0JHZ44B8A==" + }, "node_modules/@hashicorp/js-utils": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@hashicorp/js-utils/-/js-utils-1.0.10.tgz", @@ -909,6 +928,17 @@ "node": ">=4" } }, + "node_modules/@hashicorp/platform-analytics": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@hashicorp/platform-analytics/-/platform-analytics-0.2.0.tgz", + "integrity": "sha512-4Pmb4Fy/2eDCZPFu/O4wKK2L5VIqwsXfDPDGX3eA4Dk67ytZ//SXuW9oFtG97ACyW/p1i72EmwoqcrR6usDwtg==", + "dependencies": { + "fathom-client": "^3.2.0" + }, + "peerDependencies": { + "react": ">= 16.x" + } + }, "node_modules/@hashicorp/platform-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@hashicorp/platform-cli/-/platform-cli-1.2.0.tgz", @@ -1175,9 +1205,9 @@ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "node_modules/@hashicorp/react-content": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@hashicorp/react-content/-/react-content-8.0.2.tgz", - "integrity": "sha512-FNE1IiXizUDPL/rsvo37uYSBsAB8LhtCianMU/h3MIj8Khcwc/qC3pTabGs9Kv3ujA+fiIp/ImzWEGAhB22T7g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@hashicorp/react-content/-/react-content-8.0.3.tgz", + "integrity": "sha512-WSmTlU3UU0YMbp1Eb9RCZqWh4skwIeXPQY/OFaNLgZqQh6/vP+j7c6pItPhnSz8w0u81XatuxVfo0cJjtxidYg==", "dependencies": { "@hashicorp/platform-product-meta": "^0.1.0", "classnames": "^2.3.1" @@ -1422,6 +1452,26 @@ "react": ">=16.x" } }, + "node_modules/@hashicorp/react-open-api-page": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hashicorp/react-open-api-page/-/react-open-api-page-2.0.1.tgz", + "integrity": "sha512-9D1rAKksUyvrdsKFA/uR54jFSO4egr8np6X8ayIPSRUSCbIqLNKl46MitXf9oddqL3xunaN2dL/cjqQ7FYycPA==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.9", + "@hashicorp/flight-icons": "^1.1.0", + "@hashicorp/platform-markdown-utils": "^0.1.3", + "@hashicorp/react-content": "^8.0.3", + "@hashicorp/react-docs-sidenav": "^8.4.0", + "@hashicorp/react-head": "^3.1.2", + "change-case": "^4.1.2", + "classnames": "^2.3.1" + } + }, + "node_modules/@hashicorp/react-open-api-page/node_modules/classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, "node_modules/@hashicorp/react-placeholder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@hashicorp/react-placeholder/-/react-placeholder-0.1.0.tgz", @@ -1614,14 +1664,6 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, - "@hashicorp/platform-analytics": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@hashicorp/platform-analytics/-/platform-analytics-0.1.0.tgz", - "integrity": "sha512-kR/E0KWemjazSrSFN9l22v8JqlQvkgjgkZHPJEhGiBt05LMtt2ugxIakv+gnzW5lU9434nnr7oDooUyj4eufPA==", - "requires": { - "fathom-client": "^3.2.0" - } - }, "node_modules/@hashicorp/react-text-input/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -2303,13 +2345,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.2.tgz", "integrity": "sha512-hZuwOlGOwBZADA8EyDYyjx3+4JGIGjSHDHWrmpI7g5rFmQNltjlbaefAbiU5Kk7j3BUSDwt30quJRFv3nyJQ0w==", - "cpu": [ - "arm64" - ], + "cpu": ["arm64"], "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": ">= 10" } @@ -2318,13 +2356,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.2.tgz", "integrity": "sha512-PGOp0E1GisU+EJJlsmJVGE+aPYD0Uh7zqgsrpD3F/Y3766Ptfbe1lEPPWnRDl+OzSSrSrX1lkyM/Jlmh5OwNvA==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": ">= 10" } @@ -2333,13 +2367,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.2.tgz", "integrity": "sha512-YcDHTJjn/8RqvyJVB6pvEKXihDcdrOwga3GfMv/QtVeLphTouY4BIcEUfrG5+26Nf37MP1ywN3RRl1TxpurAsQ==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">= 10" } @@ -2348,13 +2378,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.2.tgz", "integrity": "sha512-e/pIKVdB+tGQYa1cW3sAeHm8gzEri/HYLZHT4WZojrUxgWXqx8pk7S7Xs47uBcFTqBDRvK3EcQpPLf3XdVsDdg==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "optional": true, - "os": [ - "win32" - ], + "os": ["win32"], "engines": { "node": ">= 10" } @@ -4443,7 +4469,7 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/bin-wrapper/node_modules/file-type": { @@ -4515,6 +4541,9 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", "dev": true, + "dependencies": { + "p-timeout": "^2.0.1" + }, "engines": { "node": ">=4" } @@ -5664,9 +5693,7 @@ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "devOptional": true, - "engines": [ - "node >= 0.8" - ], + "engines": ["node >= 0.8"], "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -7298,7 +7325,7 @@ "esutils": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/eslint-plugin-import/node_modules/hosted-git-info": { @@ -7572,11 +7599,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "fathom-client": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fathom-client/-/fathom-client-3.2.0.tgz", - "integrity": "sha512-WF/qA5wXYSuA5K8uiIhGNbErOcTAmfLEWrBxWP2px2dEc9waH9zxjdh9k0F907VCBhdJrv+f7V3HT/Pmro40zA==" - }, "node_modules/eslint/node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -8279,7 +8301,7 @@ "version": "3.19.0", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz", "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==", - "dev": true, + "devOptional": true, "bin": { "xml2js": "cli.js" }, @@ -8303,6 +8325,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fathom-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fathom-client/-/fathom-client-3.2.0.tgz", + "integrity": "sha512-WF/qA5wXYSuA5K8uiIhGNbErOcTAmfLEWrBxWP2px2dEc9waH9zxjdh9k0F907VCBhdJrv+f7V3HT/Pmro40zA==" + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -8508,7 +8535,10 @@ "locate-path": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/find-versions": { @@ -8771,9 +8801,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -10376,7 +10404,10 @@ "kind-of": "^6.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-date-object": { @@ -10588,10 +10619,13 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "devOptional": true, "dependencies": { - "isobject": "^3.0.1" + "fast-xml-parser": "^3.19.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-png": { @@ -10661,14 +10695,8 @@ "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.1.tgz", "integrity": "sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA==", "dev": true, - "dependencies": { - "fast-xml-parser": "^3.19.0" - }, - "engines": { - "node": ">=6" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-symbol": { @@ -10892,7 +10920,10 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -11209,8 +11240,8 @@ "engines": { "node": ">=10" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, "node_modules/lint-staged/node_modules/has-flag": { @@ -12383,7 +12414,7 @@ "is-extendable": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=4.0.0" } }, "node_modules/mixin-deep/node_modules/is-extendable": { @@ -19476,9 +19507,7 @@ "dev": true, "hasInstallScript": true, "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "peer": true, "dependencies": { "bindings": "^1.5.0", @@ -20963,6 +20992,11 @@ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" }, + "@hashicorp/flight-icons": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hashicorp/flight-icons/-/flight-icons-1.2.0.tgz", + "integrity": "sha512-gMsPGbDXSBWe4v7sFjG+ZlQdc8ipNLi5w98muaKU5tUNG027b5XNy5chkiT1/gsygi5ZlKXU0klgC0JHZ44B8A==" + }, "@hashicorp/js-utils": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@hashicorp/js-utils/-/js-utils-1.0.10.tgz", @@ -21006,6 +21040,14 @@ } } }, + "@hashicorp/platform-analytics": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@hashicorp/platform-analytics/-/platform-analytics-0.2.0.tgz", + "integrity": "sha512-4Pmb4Fy/2eDCZPFu/O4wKK2L5VIqwsXfDPDGX3eA4Dk67ytZ//SXuW9oFtG97ACyW/p1i72EmwoqcrR6usDwtg==", + "requires": { + "fathom-client": "^3.2.0" + } + }, "@hashicorp/platform-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@hashicorp/platform-cli/-/platform-cli-1.2.0.tgz", @@ -21230,9 +21272,9 @@ } }, "@hashicorp/react-content": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@hashicorp/react-content/-/react-content-8.0.2.tgz", - "integrity": "sha512-FNE1IiXizUDPL/rsvo37uYSBsAB8LhtCianMU/h3MIj8Khcwc/qC3pTabGs9Kv3ujA+fiIp/ImzWEGAhB22T7g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@hashicorp/react-content/-/react-content-8.0.3.tgz", + "integrity": "sha512-WSmTlU3UU0YMbp1Eb9RCZqWh4skwIeXPQY/OFaNLgZqQh6/vP+j7c6pItPhnSz8w0u81XatuxVfo0cJjtxidYg==", "requires": { "@hashicorp/platform-product-meta": "^0.1.0", "classnames": "^2.3.1" @@ -21430,6 +21472,28 @@ "gray-matter": "4.0.2" } }, + "@hashicorp/react-open-api-page": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hashicorp/react-open-api-page/-/react-open-api-page-2.0.1.tgz", + "integrity": "sha512-9D1rAKksUyvrdsKFA/uR54jFSO4egr8np6X8ayIPSRUSCbIqLNKl46MitXf9oddqL3xunaN2dL/cjqQ7FYycPA==", + "requires": { + "@apidevtools/json-schema-ref-parser": "^9.0.9", + "@hashicorp/flight-icons": "^1.1.0", + "@hashicorp/platform-markdown-utils": "^0.1.3", + "@hashicorp/react-content": "^8.0.3", + "@hashicorp/react-docs-sidenav": "^8.4.0", + "@hashicorp/react-head": "^3.1.2", + "change-case": "^4.1.2", + "classnames": "^2.3.1" + }, + "dependencies": { + "classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + } + } + }, "@hashicorp/react-placeholder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@hashicorp/react-placeholder/-/react-placeholder-0.1.0.tgz", @@ -23889,7 +23953,10 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", - "dev": true + "dev": true, + "requires": { + "p-timeout": "^2.0.1" + } }, "p-event": { "version": "2.3.1", @@ -26935,7 +27002,7 @@ "version": "3.19.0", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz", "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==", - "dev": true + "devOptional": true }, "fastest-levenshtein": { "version": "1.0.12", @@ -26952,6 +27019,11 @@ "reusify": "^1.0.4" } }, + "fathom-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fathom-client/-/fathom-client-3.2.0.tgz", + "integrity": "sha512-WF/qA5wXYSuA5K8uiIhGNbErOcTAmfLEWrBxWP2px2dEc9waH9zxjdh9k0F907VCBhdJrv+f7V3HT/Pmro40zA==" + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -28697,7 +28769,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "devOptional": true, "requires": { - "isobject": "^3.0.1" + "fast-xml-parser": "^3.19.0" } }, "is-png": { @@ -28742,10 +28814,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.1.tgz", "integrity": "sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA==", - "dev": true, - "requires": { - "fast-xml-parser": "^3.19.0" - } + "dev": true }, "is-symbol": { "version": "1.0.4", @@ -29162,7 +29231,10 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } }, "has-flag": { "version": "4.0.0", diff --git a/website/package.json b/website/package.json index 58a842a6e..4e787564e 100644 --- a/website/package.json +++ b/website/package.json @@ -7,7 +7,7 @@ "@apidevtools/json-schema-ref-parser": "9.0.9", "@hashicorp/mktg-global-styles": "^4.0.0", "@hashicorp/mktg-logos": "^1.2.0", - "@hashicorp/platform-analytics": "^0.1.0", + "@hashicorp/platform-analytics": "^0.2.0", "@hashicorp/platform-code-highlighting": "^0.1.2", "@hashicorp/platform-runtime-error-monitoring": "^0.1.0", "@hashicorp/platform-util": "^0.1.0", @@ -22,6 +22,7 @@ "@hashicorp/react-hero": "^8.0.2", "@hashicorp/react-image": "^4.0.3", "@hashicorp/react-markdown-page": "^1.4.3", + "@hashicorp/react-open-api-page": "^2.0.1", "@hashicorp/react-product-downloads-page": "^2.5.2", "@hashicorp/react-product-features-list": "^5.0.0", "@hashicorp/react-search": "^6.1.1", @@ -33,7 +34,6 @@ "classnames": "2.2.6", "next": "^11.1.2", "next-mdx-remote": "^3.0.4", - "fathom-client": "^3.2.0", "next-remote-watch": "1.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/website/pages/api-docs/[[...page]].jsx b/website/pages/api-docs/[[...page]].jsx index eb752b15f..4fa73554f 100644 --- a/website/pages/api-docs/[[...page]].jsx +++ b/website/pages/api-docs/[[...page]].jsx @@ -1,10 +1,12 @@ import { productName, productSlug } from 'data/metadata' +import OpenApiPage from '@hashicorp/react-open-api-page' +/* Used server-side only */ import path from 'path' -import parseSwagger from '../../lib/swagger-parser' -import OpenApiPage, { +import { getPathsFromSchema, getPropsForPage, -} from '../../components/openapi-page' +} from '@hashicorp/react-open-api-page/server' +import { processSchemaFile } from '@hashicorp/react-open-api-page/process-schema' const targetFile = '../internal/gen/controller.swagger.json' const pathFromRoot = 'api-docs' @@ -15,19 +17,21 @@ export default function OpenApiDocsPage(props) { {...props} productName={productName} productSlug={productSlug} - pathFromRoot={pathFromRoot} + baseRoute={pathFromRoot} /> ) } export async function getStaticPaths() { - const schema = await parseSwagger(path.join(process.cwd(), targetFile)) + const swaggerFile = path.join(process.cwd(), targetFile) + const schema = await processSchemaFile(swaggerFile) const paths = getPathsFromSchema(schema) return { paths, fallback: false } } export async function getStaticProps({ params }) { - const schema = await parseSwagger(path.join(process.cwd(), targetFile)) + const swaggerFile = path.join(process.cwd(), targetFile) + const schema = await processSchemaFile(swaggerFile) const props = getPropsForPage(schema, params) return { props } } diff --git a/website/public/img/boundary.png b/website/public/img/boundary.png new file mode 100644 index 000000000..23597cdb5 Binary files /dev/null and b/website/public/img/boundary.png differ