diff --git a/.github/workflows/action-lint.yaml b/.github/workflows/action-lint.yaml new file mode 100644 index 000000000..f41be8177 --- /dev/null +++ b/.github/workflows/action-lint.yaml @@ -0,0 +1,24 @@ +name: "🔦 actionlint" + +on: + pull_request: + branches: + - main + paths: + - '.github/workflows/**' + +jobs: + actionlint: + runs-on: ubuntu-22.04 + name: actionlint + permissions: + contents: read + pull-requests: write + checks: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: "Run reviewdog actionlint" + uses: reviewdog/action-actionlint@053981cb135d7a696bbeec6181d9d5fae6e07dae # v1.57.0 + with: + reporter: "github-pr-review" + fail_on_error: true \ No newline at end of file diff --git a/.github/workflows/release-build.yaml b/.github/workflows/release-build.yaml new file mode 100644 index 000000000..c128c62de --- /dev/null +++ b/.github/workflows/release-build.yaml @@ -0,0 +1,88 @@ +name: Build Platform Container Image + +on: + release: + types: [published] + + +jobs: + build: + if: startsWith(github.event.release.tag_name, 'service/') + runs-on: ubuntu-22.04 + permissions: + id-token: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - name: "Authenticate to Google Cloud (Push to Public registry)" + id: "gcp-auth" + uses: google-github-actions/auth@62cf5bd3e4211a0a0b51f2c6d6a37129d828611d + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + token_format: "access_token" + create_credentials_file: false + + - name: Install Cosign + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da + + - name: Install Trivy + uses: aquasecurity/setup-trivy@ff1b8b060f23b650436d419b5e13f67f5d4c3087 + with: + version: v0.57.1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb + + - name: 'Docker login to Artifact Registry' + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 + with: + registry: us-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.gcp-auth.outputs.access_token }} + + - id: docker_meta + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 + with: + images: ${{ secrets.DOCKER_REPO }} + tags: | + type=sha,format=long + type=match,pattern=service/v(\d.\d.\d),group=1,prefix=v + type=match,pattern=service/v(\d.\d),group=1,prefix=v + labels: | + org.opencontainers.image.documentation=https://docs.opentdf.io + + - name: Build and Push container images + uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c + id: build-and-push + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.docker_meta.outputs.tags }} + + - name: Sign the images with GitHub OIDC Token + env: + DIGEST: ${{ steps.build-and-push.outputs.digest }} + TAGS: ${{ steps.docker_meta.outputs.tags }} + run: | + images="" + for tag in ${TAGS}; do + images+="${tag}@${DIGEST} " + done + # shellcheck disable=SC2086 + cosign sign --yes ${images} + + - name: Generate Reports + run: | + trivy image --scanners vuln --format cyclonedx --output bom-cyclonedx.json ${{ secrets.DOCKER_REPO }}@${{ steps.build-and-push.outputs.digest }} + trivy image --format spdx-json --output bom-spdx.json ${{ secrets.DOCKER_REPO }}@${{ steps.build-and-push.outputs.digest }} + trivy image --format cosign-vuln --output cosign-vuln.json ${{ secrets.DOCKER_REPO }}@${{ steps.build-and-push.outputs.digest }} + + - name: Cosign Attest SBOM + run: | + cosign attest --type cyclonedx --predicate bom-cyclonedx.json '${{ secrets.DOCKER_REPO }}@${{ steps.build-and-push.outputs.digest }}' + cosign attest --type spdxjson -predicate bom-spdx.json '${{ secrets.DOCKER_REPO }}@${{ steps.build-and-push.outputs.digest }}' + cosign attest --type vuln --predicate cosign-vuln.json '${{ secrets.DOCKER_REPO }}@${{ steps.build-and-push.outputs.digest }}' \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a417fa0cb..ced5b0bfb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -4,5 +4,5 @@ "lib/flattening": "0.1.2", "protocol/go": "0.2.22", "sdk": "0.3.23", - "service": "0.4.30" + "service": "0.4.31" } diff --git a/service/CHANGELOG.md b/service/CHANGELOG.md index 45416b05f..aa59bf0a1 100644 --- a/service/CHANGELOG.md +++ b/service/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.4.31](https://github.com/opentdf/platform/compare/service/v0.4.30...service/v0.4.31) (2024-12-02) + + +### Features + +* **kas:** collect metrics ([#1702](https://github.com/opentdf/platform/issues/1702)) ([def28d1](https://github.com/opentdf/platform/commit/def28d1984b0b111a07330a3eb59c1285206062d)) +* **policy:** 1500 Attribute create with Values (one RPC Call) should employ a db transaction ([#1778](https://github.com/opentdf/platform/issues/1778)) ([90edbde](https://github.com/opentdf/platform/commit/90edbde92ea63ad488b9a6de09fcffbc7a4380de)) + + +### Bug Fixes + +* **core:** move auth interceptor to top of chain ([#1790](https://github.com/opentdf/platform/issues/1790)) ([f9f5a75](https://github.com/opentdf/platform/commit/f9f5a7545827c5d8cef7f536963e4f794a7f3f6c)) +* **policy:** return fqns in list subject mappings ([#1796](https://github.com/opentdf/platform/issues/1796)) ([c0a9dda](https://github.com/opentdf/platform/commit/c0a9dda975a9384cea8efc413d567edce13f753f)) + ## [0.4.30](https://github.com/opentdf/platform/compare/service/v0.4.29...service/v0.4.30) (2024-11-27) diff --git a/service/cmd/policy.go b/service/cmd/policy.go index eea0bca5f..58a7daa22 100644 --- a/service/cmd/policy.go +++ b/service/cmd/policy.go @@ -44,21 +44,29 @@ var ( panic(fmt.Errorf("could not load config: %w", err)) } - res := dbClient.AttrFqnReindex(context.Background()) - cmd.Print("Namespace FQNs reindexed:\n") - for _, r := range res.Namespaces { - cmd.Printf("\t%s: %s\n", r.ID, r.Fqn) - } + ctx := context.Background() - cmd.Print("Attribute FQNs reindexed:\n") - for _, r := range res.Attributes { - cmd.Printf("\t%s: %s\n", r.ID, r.Fqn) - } + // ignore error as dbClient.AttrFqnReindex will panic on error + _ = dbClient.RunInTx(ctx, func(txClient *policydb.PolicyDBClient) error { + res := txClient.AttrFqnReindex(ctx) - cmd.Print("Attribute Value FQNs reindexed:\n") - for _, r := range res.Values { - cmd.Printf("\t%s: %s\n", r.ID, r.Fqn) - } + cmd.Print("Namespace FQNs reindexed:\n") + for _, r := range res.Namespaces { + cmd.Printf("\t%s: %s\n", r.ID, r.Fqn) + } + + cmd.Print("Attribute FQNs reindexed:\n") + for _, r := range res.Attributes { + cmd.Printf("\t%s: %s\n", r.ID, r.Fqn) + } + + cmd.Print("Attribute Value FQNs reindexed:\n") + for _, r := range res.Values { + cmd.Printf("\t%s: %s\n", r.ID, r.Fqn) + } + + return nil + }) }, } ) diff --git a/service/integration/policy_test.go b/service/integration/policy_test.go new file mode 100644 index 000000000..986828791 --- /dev/null +++ b/service/integration/policy_test.go @@ -0,0 +1,128 @@ +package integration + +import ( + "context" + "fmt" + "log/slog" + "testing" + + "github.com/opentdf/platform/service/internal/fixtures" + "github.com/opentdf/platform/service/policy/db" + "github.com/stretchr/testify/suite" +) + +type PolicyDBClientSuite struct { + suite.Suite + f fixtures.Fixtures + db fixtures.DBInterface + ctx context.Context //nolint:containedctx // context is used in the test suite +} + +func (s *PolicyDBClientSuite) SetupSuite() { + s.ctx = context.Background() + c := *Config + c.DB.Schema = "text_opentdf_policy_db_client" + s.db = fixtures.NewDBInterface(c) + s.f = fixtures.NewFixture(s.db) + s.f.Provision() +} + +func (s *PolicyDBClientSuite) TearDownSuite() { + slog.Info("tearing down db.PolicyDbClient test suite") + s.f.TearDown() +} + +func (s *PolicyDBClientSuite) Test_RunInTx_CommitsOnSuccess() { + var ( + nsName = "success.com" + attrName = fmt.Sprintf("http://%s/attr/attr_one", nsName) + attrValue = fmt.Sprintf("http://%s/attr/%s/value/attr_one_value", nsName, attrName) + + nsID string + attrID string + valID string + err error + ) + + txErr := s.db.PolicyClient.RunInTx(s.ctx, func(txClient *db.PolicyDBClient) error { + nsID, err = txClient.Queries.CreateNamespace(s.ctx, db.CreateNamespaceParams{ + Name: nsName, + }) + s.Require().NoError(err) + s.Require().NotNil(nsID) + + attrID, err = txClient.Queries.CreateAttribute(s.ctx, db.CreateAttributeParams{ + NamespaceID: nsID, + Name: attrName, + Rule: db.AttributeDefinitionRuleALLOF, + }) + s.Require().NoError(err) + s.Require().NotNil(attrID) + + valID, err = txClient.Queries.CreateAttributeValue(s.ctx, db.CreateAttributeValueParams{ + AttributeDefinitionID: attrID, + Value: attrValue, + }) + s.Require().NoError(err) + s.Require().NotNil(valID) + + return nil + }) + s.Require().NoError(txErr) + + ns, err := s.db.PolicyClient.GetNamespace(s.ctx, nsID) + s.Require().NoError(err) + s.Equal(nsName, ns.GetName()) + + attr, err := s.db.PolicyClient.GetAttribute(s.ctx, attrID) + s.Require().NoError(err) + s.Equal(attrName, attr.GetName()) + + attrVal, err := s.db.PolicyClient.GetAttributeValue(s.ctx, valID) + s.Require().NoError(err) + s.Equal(attrValue, attrVal.GetValue()) +} + +func (s *PolicyDBClientSuite) Test_RunInTx_RollsBackOnFailure() { + var ( + nsName = "failure.com" + attrName = fmt.Sprintf("http://%s/attr/attr_one", nsName) + + nsID string + attrID string + err error + ) + + txErr := s.db.PolicyClient.RunInTx(s.ctx, func(txClient *db.PolicyDBClient) error { + nsID, err = txClient.Queries.CreateNamespace(s.ctx, db.CreateNamespaceParams{ + Name: nsName, + }) + s.Require().NoError(err) + s.Require().NotNil(nsID) + + attrID, err = txClient.Queries.CreateAttribute(s.ctx, db.CreateAttributeParams{ + NamespaceID: "invalid_ns_id", + Name: attrName, + Rule: db.AttributeDefinitionRuleALLOF, + }) + s.Require().Error(err) + s.Require().Zero(attrID) + return err + }) + s.Require().Error(txErr) + + ns, err := s.db.PolicyClient.GetNamespace(s.ctx, nsID) + s.Require().Error(err) + s.Nil(ns) + + attr, err := s.db.PolicyClient.GetAttribute(s.ctx, attrID) + s.Require().Error(err) + s.Nil(attr) +} + +func TestPolicySuite(t *testing.T) { + if testing.Short() { + t.Skip("skipping policy integration tests") + } + suite.Run(t, new(PolicyDBClientSuite)) +} diff --git a/service/integration/subject_mappings_test.go b/service/integration/subject_mappings_test.go index a70ff0fc4..541537e7c 100644 --- a/service/integration/subject_mappings_test.go +++ b/service/integration/subject_mappings_test.go @@ -404,14 +404,17 @@ func (s *SubjectMappingsSuite) Test_ListSubjectMappings_NoPagination_Succeeds() for _, sm := range listed { if sm.GetId() == fixture1.ID { assertEqual(sm, fixture1) + s.Equal("https://example.com/attr/attr1/value/value1", sm.GetAttributeValue().GetFqn()) found1 = true } if sm.GetId() == fixture2.ID { assertEqual(sm, fixture2) + s.Equal("https://example.com/attr/attr1/value/value2", sm.GetAttributeValue().GetFqn()) found2 = true } if sm.GetId() == fixture3.ID { assertEqual(sm, fixture3) + s.Equal("https://example.com/attr/attr1/value/value1", sm.GetAttributeValue().GetFqn()) found3 = true } } diff --git a/service/pkg/db/db.go b/service/pkg/db/db.go index 038ee0543..3db39d11e 100644 --- a/service/pkg/db/db.go +++ b/service/pkg/db/db.go @@ -54,6 +54,7 @@ func (t Table) Field(field string) string { // We can rename this but wanted to get mocks working. type PgxIface interface { Acquire(ctx context.Context) (*pgxpool.Conn, error) + Begin(ctx context.Context) (pgx.Tx, error) Exec(context.Context, string, ...any) (pgconn.CommandTag, error) QueryRow(context.Context, string, ...any) pgx.Row Query(context.Context, string, ...any) (pgx.Rows, error) diff --git a/service/pkg/db/errors.go b/service/pkg/db/errors.go index 74a159dd4..c848483a5 100644 --- a/service/pkg/db/errors.go +++ b/service/pkg/db/errors.go @@ -22,6 +22,9 @@ var ( ErrUUIDInvalid = errors.New("ErrUUIDInvalid: value not a valid UUID") ErrMissingValue = errors.New("ErrMissingValue: value must be included") ErrListLimitTooLarge = errors.New("ErrListLimitTooLarge: requested limit greater than configured maximum") + ErrTxBeginFailed = errors.New("ErrTxBeginFailed: failed to begin DB transaction") + ErrTxRollbackFailed = errors.New("ErrTxRollbackFailed: failed to rollback DB transaction") + ErrTxCommitFailed = errors.New("ErrTxCommitFailed: failed to commit DB transaction") ) // Get helpful error message for PostgreSQL violation diff --git a/service/policy/attributes/attributes.go b/service/policy/attributes/attributes.go index da59557ff..b8e4fd083 100644 --- a/service/policy/attributes/attributes.go +++ b/service/policy/attributes/attributes.go @@ -54,19 +54,26 @@ func (s AttributesService) CreateAttribute(ctx context.Context, ActionType: audit.ActionTypeCreate, } - item, err := s.dbClient.CreateAttribute(ctx, req.Msg) + err := s.dbClient.RunInTx(ctx, func(txClient *policydb.PolicyDBClient) error { + item, err := txClient.CreateAttribute(ctx, req.Msg) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } + + s.logger.Debug("created new attribute definition", slog.String("name", req.Msg.GetName())) + + auditParams.ObjectID = item.GetId() + auditParams.Original = item + s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) + + rsp.Attribute = item + return nil + }) if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) return nil, db.StatusifyError(err, db.ErrTextCreationFailed, slog.String("attribute", req.Msg.String())) } - s.logger.Debug("created new attribute definition", slog.String("name", req.Msg.GetName())) - - auditParams.ObjectID = item.GetId() - auditParams.Original = item - s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) - - rsp.Attribute = item return connect.NewResponse(rsp), nil } @@ -193,17 +200,25 @@ func (s *AttributesService) CreateAttributeValue(ctx context.Context, req *conne ActionType: audit.ActionTypeCreate, } - item, err := s.dbClient.CreateAttributeValue(ctx, req.Msg.GetAttributeId(), req.Msg) + err := s.dbClient.RunInTx(ctx, func(txClient *policydb.PolicyDBClient) error { + item, err := txClient.CreateAttributeValue(ctx, req.Msg.GetAttributeId(), req.Msg) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } + + auditParams.ObjectID = item.GetId() + auditParams.Original = item + s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) + + rsp.Value = item + + return nil + }) if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextCreationFailed, slog.String("attributeId", req.Msg.GetAttributeId()), slog.String("value", req.Msg.String())) + return nil, db.StatusifyError(err, db.ErrTextCreationFailed, slog.String("value", req.Msg.String())) } - auditParams.ObjectID = item.GetId() - auditParams.Original = item - s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) - - rsp.Value = item return connect.NewResponse(rsp), nil } diff --git a/service/policy/db/policy.go b/service/policy/db/policy.go index 390362b3d..c70db6349 100644 --- a/service/policy/db/policy.go +++ b/service/policy/db/policy.go @@ -1,6 +1,9 @@ package db import ( + "context" + "fmt" + "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/service/logger" "github.com/opentdf/platform/service/pkg/db" @@ -31,6 +34,33 @@ func NewClient(c *db.Client, logger *logger.Logger, configuredListLimitMax, conf return PolicyDBClient{c, logger, New(c.Pgx), ListConfig{limitDefault: configuredListLimitDefault, limitMax: configuredListLimitMax}} } +func (c *PolicyDBClient) RunInTx(ctx context.Context, query func(txClient *PolicyDBClient) error) error { + tx, err := c.Client.Pgx.Begin(ctx) + if err != nil { + return fmt.Errorf("%w: %w", db.ErrTxBeginFailed, err) + } + + txClient := &PolicyDBClient{c.Client, c.logger, c.Queries.WithTx(tx), c.listCfg} + + err = query(txClient) + if err != nil { + c.logger.WarnContext(ctx, "error during DB transaction, rolling back") + + if rollbackErr := tx.Rollback(ctx); rollbackErr != nil { + // this should never happen, but if it does, we want to know about it + return fmt.Errorf("%w, transaction [%w]: %w", db.ErrTxRollbackFailed, err, rollbackErr) + } + + return err + } + + if err = tx.Commit(ctx); err != nil { + return fmt.Errorf("%w: %w", db.ErrTxCommitFailed, err) + } + + return nil +} + func getDBStateTypeTransformedEnum(state common.ActiveStateEnum) transformedState { switch state.String() { case common.ActiveStateEnum_ACTIVE_STATE_ENUM_ACTIVE.String(): diff --git a/service/policy/db/query.sql b/service/policy/db/query.sql index dc36dd37a..db10abfb5 100644 --- a/service/policy/db/query.sql +++ b/service/policy/db/query.sql @@ -839,13 +839,19 @@ SELECT 'metadata', JSON_STRIP_NULLS(JSON_BUILD_OBJECT('labels', scs.metadata->'labels', 'created_at', scs.created_at, 'updated_at', scs.updated_at)), 'subject_sets', scs.condition ) AS subject_condition_set, - JSON_BUILD_OBJECT('id', av.id,'value', av.value,'active', av.active) AS attribute_value, + JSON_BUILD_OBJECT( + 'id', av.id, + 'value', av.value, + 'active', av.active, + 'fqn', fqns.fqn + ) AS attribute_value, counted.total FROM subject_mappings sm CROSS JOIN counted LEFT JOIN attribute_values av ON sm.attribute_value_id = av.id +LEFT JOIN attribute_fqns fqns ON av.id = fqns.value_id LEFT JOIN subject_condition_set scs ON scs.id = sm.subject_condition_set_id -GROUP BY av.id, sm.id, scs.id, counted.total +GROUP BY av.id, sm.id, scs.id, counted.total, fqns.fqn LIMIT @limit_ OFFSET @offset_; diff --git a/service/policy/db/query.sql.go b/service/policy/db/query.sql.go index 3e21083c0..acceb421f 100644 --- a/service/policy/db/query.sql.go +++ b/service/policy/db/query.sql.go @@ -2248,13 +2248,14 @@ SELECT 'metadata', JSON_STRIP_NULLS(JSON_BUILD_OBJECT('labels', scs.metadata->'labels', 'created_at', scs.created_at, 'updated_at', scs.updated_at)), 'subject_sets', scs.condition ) AS subject_condition_set, - JSON_BUILD_OBJECT('id', av.id,'value', av.value,'active', av.active) AS attribute_value, + JSON_BUILD_OBJECT('id', av.id,'value', av.value,'active', av.active, 'fqn',fqns.fqn) AS attribute_value, counted.total FROM subject_mappings sm CROSS JOIN counted LEFT JOIN attribute_values av ON sm.attribute_value_id = av.id +LEFT JOIN attribute_fqns fqns ON av.id = fqns.value_id LEFT JOIN subject_condition_set scs ON scs.id = sm.subject_condition_set_id -GROUP BY av.id, sm.id, scs.id, counted.total +GROUP BY av.id, sm.id, scs.id, counted.total, fqns.fqn LIMIT $2 OFFSET $1 ` @@ -2290,13 +2291,14 @@ type ListSubjectMappingsRow struct { // 'metadata', JSON_STRIP_NULLS(JSON_BUILD_OBJECT('labels', scs.metadata->'labels', 'created_at', scs.created_at, 'updated_at', scs.updated_at)), // 'subject_sets', scs.condition // ) AS subject_condition_set, -// JSON_BUILD_OBJECT('id', av.id,'value', av.value,'active', av.active) AS attribute_value, +// JSON_BUILD_OBJECT('id', av.id,'value', av.value,'active', av.active, 'fqn',fqns.fqn) AS attribute_value, // counted.total // FROM subject_mappings sm // CROSS JOIN counted // LEFT JOIN attribute_values av ON sm.attribute_value_id = av.id +// LEFT JOIN attribute_fqns fqns ON av.id = fqns.value_id // LEFT JOIN subject_condition_set scs ON scs.id = sm.subject_condition_set_id -// GROUP BY av.id, sm.id, scs.id, counted.total +// GROUP BY av.id, sm.id, scs.id, counted.total, fqns.fqn // LIMIT $2 // OFFSET $1 func (q *Queries) ListSubjectMappings(ctx context.Context, arg ListSubjectMappingsParams) ([]ListSubjectMappingsRow, error) { diff --git a/service/policy/namespaces/namespaces.go b/service/policy/namespaces/namespaces.go index 7ae3d7181..93150ff71 100644 --- a/service/policy/namespaces/namespaces.go +++ b/service/policy/namespaces/namespaces.go @@ -98,19 +98,26 @@ func (ns NamespacesService) CreateNamespace(ctx context.Context, req *connect.Re } rsp := &namespaces.CreateNamespaceResponse{} - n, err := ns.dbClient.CreateNamespace(ctx, req.Msg) + err := ns.dbClient.RunInTx(ctx, func(txClient *policydb.PolicyDBClient) error { + n, err := txClient.CreateNamespace(ctx, req.Msg) + if err != nil { + ns.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } + + auditParams.ObjectID = n.GetId() + auditParams.Original = n + ns.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) + + ns.logger.Debug("created new namespace", slog.String("name", req.Msg.GetName())) + rsp.Namespace = n + + return nil + }) if err != nil { - ns.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextCreationFailed, slog.String("name", req.Msg.GetName())) + return nil, db.StatusifyError(err, db.ErrTextCreationFailed, slog.String("namespace", req.Msg.String())) } - auditParams.ObjectID = n.GetId() - auditParams.Original = n - ns.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) - - ns.logger.Debug("created new namespace", slog.String("name", req.Msg.GetName())) - rsp.Namespace = n - return connect.NewResponse(rsp), nil } diff --git a/service/policy/unsafe/unsafe.go b/service/policy/unsafe/unsafe.go index 8c78cfa0c..1a9c3a586 100644 --- a/service/policy/unsafe/unsafe.go +++ b/service/policy/unsafe/unsafe.go @@ -57,25 +57,32 @@ func (s *UnsafeService) UnsafeUpdateNamespace(ctx context.Context, req *connect. ObjectID: id, } - original, err := s.dbClient.GetNamespace(ctx, id) - if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextGetRetrievalFailed, slog.String("id", id)) - } - - updated, err := s.dbClient.UnsafeUpdateNamespace(ctx, id, name) + err := s.dbClient.RunInTx(ctx, func(txClient *policydb.PolicyDBClient) error { + original, err := txClient.GetNamespace(ctx, id) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } + + updated, err := txClient.UnsafeUpdateNamespace(ctx, id, name) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } + + auditParams.Original = original + auditParams.Updated = updated + + s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) + + rsp.Namespace = &policy.Namespace{ + Id: id, + } + + return nil + }) if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextUpdateFailed, slog.String("id", id), slog.String("namespace", name)) - } - - auditParams.Original = original - auditParams.Updated = updated - - s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) - - rsp.Namespace = &policy.Namespace{ - Id: id, + return nil, db.StatusifyError(err, db.ErrTextUpdateFailed, slog.String("namespace", req.Msg.String())) } return connect.NewResponse(rsp), nil @@ -163,25 +170,32 @@ func (s *UnsafeService) UnsafeUpdateAttribute(ctx context.Context, req *connect. ObjectID: id, } - original, err := s.dbClient.GetAttribute(ctx, id) - if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextGetRetrievalFailed, slog.String("id", id)) - } + err := s.dbClient.RunInTx(ctx, func(txClient *policydb.PolicyDBClient) error { + original, err := txClient.GetAttribute(ctx, id) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } - updated, err := s.dbClient.UnsafeUpdateAttribute(ctx, req.Msg) - if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextUpdateFailed, slog.String("id", id), slog.String("attribute", req.Msg.String())) - } + updated, err := txClient.UnsafeUpdateAttribute(ctx, req.Msg) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } - auditParams.Original = original - auditParams.Updated = updated + auditParams.Original = original + auditParams.Updated = updated - s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) + s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) - rsp.Attribute = &policy.Attribute{ - Id: id, + rsp.Attribute = &policy.Attribute{ + Id: id, + } + + return nil + }) + if err != nil { + return nil, db.StatusifyError(err, db.ErrTextUpdateFailed, slog.String("attribute", req.Msg.String())) } return connect.NewResponse(rsp), nil @@ -269,26 +283,34 @@ func (s *UnsafeService) UnsafeUpdateAttributeValue(ctx context.Context, req *con ObjectID: id, } - original, err := s.dbClient.GetAttributeValue(ctx, id) - if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextGetRetrievalFailed, slog.String("id", id)) - } + err := s.dbClient.RunInTx(ctx, func(txClient *policydb.PolicyDBClient) error { + original, err := txClient.GetAttributeValue(ctx, id) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } - updated, err := s.dbClient.UnsafeUpdateAttributeValue(ctx, req.Msg) - if err != nil { - s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) - return nil, db.StatusifyError(err, db.ErrTextUpdateFailed, slog.String("id", id), slog.String("attribute_value", req.Msg.String())) - } + updated, err := txClient.UnsafeUpdateAttributeValue(ctx, req.Msg) + if err != nil { + s.logger.Audit.PolicyCRUDFailure(ctx, auditParams) + return err + } - auditParams.Original = original - auditParams.Updated = updated + auditParams.Original = original + auditParams.Updated = updated - s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) + s.logger.Audit.PolicyCRUDSuccess(ctx, auditParams) - rsp.Value = &policy.Value{ - Id: id, + rsp.Value = &policy.Value{ + Id: id, + } + + return nil + }) + if err != nil { + return nil, db.StatusifyError(err, db.ErrTextUpdateFailed, slog.String("value", req.Msg.String())) } + return connect.NewResponse(rsp), nil }