diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 26f592678..63f37612b 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -90,6 +90,8 @@ jobs: integration: name: integration tests runs-on: ubuntu-22.04 + env: + TLS_ENABLED: "true" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 @@ -105,9 +107,18 @@ jobs: run: ./.github/scripts/work-init.sh - run: go mod download - run: go mod verify + - name: Install mkcert + run: | + sudo apt-get install -y libnss3-tools + curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" + chmod +x mkcert-v*-linux-amd64 + sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert - run: | .github/scripts/init-temp-keys.sh + mkcert -install + mkcert -cert-file ./keys/platform.crt -key-file ./keys/platform-key.pem localhost cp opentdf-dev.yaml opentdf.yaml + yq eval '.server.tls.enabled = true' -i opentdf.yaml - name: Added Trusted Certs run: | sudo chmod -R 777 ./keys diff --git a/examples/cmd/examples.go b/examples/cmd/examples.go index 94c0c885b..6eea627dc 100644 --- a/examples/cmd/examples.go +++ b/examples/cmd/examples.go @@ -2,11 +2,12 @@ package cmd import ( "fmt" - "google.golang.org/grpc/resolver" "log" "os" "strings" + "google.golang.org/grpc/resolver" + "github.com/opentdf/platform/sdk" "github.com/spf13/cobra" ) @@ -30,7 +31,7 @@ func init() { func newSDK() (*sdk.SDK, error) { resolver.SetDefaultScheme("passthrough") - opts := []sdk.Option{sdk.WithStoreCollectionHeaders(), sdk.WithInsecurePlaintextConn()} + opts := []sdk.Option{sdk.WithStoreCollectionHeaders()} if clientCredentials != "" { i := strings.Index(clientCredentials, ":") if i < 0 { diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index 4a8509647..7c1f5d2b9 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -33,6 +33,10 @@ services: email: true username: true server: + tls: + enabled: false + cert: ./keys/platform.crt + key: ./keys/platform-key.pem auth: enabled: true enforceDPoP: false diff --git a/service/pkg/server/services.go b/service/pkg/server/services.go index 8760a5fbd..637f96c1d 100644 --- a/service/pkg/server/services.go +++ b/service/pkg/server/services.go @@ -22,6 +22,7 @@ import ( "github.com/opentdf/platform/service/policy" wellknown "github.com/opentdf/platform/service/wellknownconfiguration" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) @@ -180,6 +181,12 @@ func startServices(ctx context.Context, cfg config.Config, otdf *server.OpenTDFS grpcGatewayDialOptions := make([]grpc.DialOption, 0) if !cfg.Server.TLS.Enabled { grpcGatewayDialOptions = append(grpcGatewayDialOptions, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else { + creds, err := credentials.NewClientTLSFromFile(cfg.Server.TLS.Cert, "") + if err != nil { + return fmt.Errorf("failed to create grpc-gateway client TLS credentials: %w", err) + } + grpcGatewayDialOptions = append(grpcGatewayDialOptions, grpc.WithTransportCredentials(creds)) } if err := svc.RegisterGRPCGatewayHandler(ctx, otdf.GRPCGatewayMux, fmt.Sprintf("localhost:%d", cfg.Server.Port), grpcGatewayDialOptions); err != nil { logger.Info("service did not register a grpc gateway handler", slog.String("namespace", ns)) diff --git a/service/rttests/rt_test.go b/service/rttests/rt_test.go index d8973e6c4..49a43737b 100644 --- a/service/rttests/rt_test.go +++ b/service/rttests/rt_test.go @@ -36,7 +36,8 @@ type TestConfig struct { var attributesToMap = []string{ "https://example.com/attr/language/value/english", "https://example.com/attr/color/value/red", - "https://example.com/attr/cards/value/queen"} + "https://example.com/attr/cards/value/queen", +} var successAttributeSets = [][]string{ {}, @@ -45,11 +46,13 @@ var successAttributeSets = [][]string{ {"https://example.com/attr/color/value/red", "https://example.com/attr/color/value/green"}, {"https://example.com/attr/cards/value/jack"}, {"https://example.com/attr/cards/value/queen"}, - {"https://example.com/attr/language/value/english", + { + "https://example.com/attr/language/value/english", "https://example.com/attr/color/value/red", "https://example.com/attr/color/value/green", "https://example.com/attr/cards/value/jack", - "https://example.com/attr/cards/value/queen"}, + "https://example.com/attr/cards/value/queen", + }, } var failureAttributeSets = [][]string{ @@ -57,19 +60,25 @@ var failureAttributeSets = [][]string{ {"https://example.com/attr/color/value/blue"}, {"https://example.com/attr/color/value/blue", "https://example.com/attr/color/value/green"}, {"https://example.com/attr/cards/value/king"}, - {"https://example.com/attr/language/value/english", + { + "https://example.com/attr/language/value/english", "https://example.com/attr/language/value/french", "https://example.com/attr/color/value/red", "https://example.com/attr/color/value/green", - "https://example.com/attr/cards/value/queen"}, - {"https://example.com/attr/language/value/english", + "https://example.com/attr/cards/value/queen", + }, + { + "https://example.com/attr/language/value/english", "https://example.com/attr/color/value/blue", "https://example.com/attr/color/value/green", - "https://example.com/attr/cards/value/queen"}, - {"https://example.com/attr/language/value/english", + "https://example.com/attr/cards/value/queen", + }, + { + "https://example.com/attr/language/value/english", "https://example.com/attr/color/value/red", "https://example.com/attr/color/value/green", - "https://example.com/attr/cards/value/king"}, + "https://example.com/attr/cards/value/king", + }, } func newTestConfig() TestConfig { @@ -91,13 +100,25 @@ func Test_RoundTrips(t *testing.T) { type RoundtripSuite struct { suite.Suite TestConfig + client *sdk.SDK } func (s *RoundtripSuite) SetupSuite() { s.TestConfig = newTestConfig() slog.Info("Test config", "", s.TestConfig) - err := s.CreateTestData() + opts := []sdk.Option{} + if os.Getenv("TLS_ENABLED") == "" { + opts = append(opts, sdk.WithInsecurePlaintextConn()) + } + + opts = append(opts, sdk.WithClientCredentials(s.TestConfig.ClientID, s.TestConfig.ClientSecret, nil)) + + sdk, err := sdk.New(s.TestConfig.PlatformEndpoint, opts...) + s.Require().NoError(err) + s.client = sdk + + err = s.CreateTestData() s.Require().NoError(err) } @@ -108,9 +129,9 @@ func (s *RoundtripSuite) Tests() { s.Run(n, func() { filename := fmt.Sprintf("test-success-%d.tdf", i) plaintext := "Running a roundtrip test!" - err := encrypt(&s.TestConfig, plaintext, attributes, filename) + err := encrypt(s.client, s.TestConfig, plaintext, attributes, filename) s.Require().NoError(err) - err = decrypt(&s.TestConfig, filename, plaintext) + err = decrypt(s.client, filename, plaintext) s.NoError(err) }) } @@ -121,30 +142,21 @@ func (s *RoundtripSuite) Tests() { s.Run(n, func() { filename := fmt.Sprintf("test-failure-%d.tdf", i) plaintext := "Running a roundtrip test!" - err := encrypt(&s.TestConfig, plaintext, attributes, filename) + err := encrypt(s.client, s.TestConfig, plaintext, attributes, filename) s.Require().NoError(err) - err = decrypt(&s.TestConfig, filename, plaintext) + err = decrypt(s.client, filename, plaintext) s.ErrorContains(err, "PermissionDenied") }) } } func (s *RoundtripSuite) CreateTestData() error { - sdk, err := sdk.New(s.TestConfig.PlatformEndpoint, - sdk.WithInsecurePlaintextConn(), - sdk.WithClientCredentials(s.TestConfig.ClientID, - s.TestConfig.ClientSecret, nil), - ) - if err != nil { - slog.Error("could not connect", slog.String("error", err.Error())) - return err - } - defer sdk.Close() + client := s.client // create namespace example.com var exampleNamespace *policy.Namespace slog.Info("listing namespaces") - listResp, err := sdk.Namespaces.ListNamespaces(context.Background(), &namespaces.ListNamespacesRequest{}) + listResp, err := client.Namespaces.ListNamespaces(context.Background(), &namespaces.ListNamespacesRequest{}) if err != nil { return err } @@ -158,7 +170,7 @@ func (s *RoundtripSuite) CreateTestData() error { if exampleNamespace == nil { slog.Info("creating new namespace") - resp, err := sdk.Namespaces.CreateNamespace(context.Background(), &namespaces.CreateNamespaceRequest{ + resp, err := client.Namespaces.CreateNamespace(context.Background(), &namespaces.CreateNamespaceRequest{ Name: "example.com", }) if err != nil { @@ -171,7 +183,7 @@ func (s *RoundtripSuite) CreateTestData() error { // Create the attributes slog.Info("creating attribute language with allOf rule") - _, err = sdk.Attributes.CreateAttribute(context.Background(), &attributes.CreateAttributeRequest{ + _, err = client.Attributes.CreateAttribute(context.Background(), &attributes.CreateAttributeRequest{ Name: "language", NamespaceId: exampleNamespace.GetId(), Rule: *policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF.Enum(), @@ -193,7 +205,7 @@ func (s *RoundtripSuite) CreateTestData() error { } slog.Info("creating attribute color with anyOf rule") - _, err = sdk.Attributes.CreateAttribute(context.Background(), &attributes.CreateAttributeRequest{ + _, err = client.Attributes.CreateAttribute(context.Background(), &attributes.CreateAttributeRequest{ Name: "color", NamespaceId: exampleNamespace.GetId(), Rule: *policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF.Enum(), @@ -215,7 +227,7 @@ func (s *RoundtripSuite) CreateTestData() error { } slog.Info("creating attribute cards with hierarchy rule") - _, err = sdk.Attributes.CreateAttribute(context.Background(), &attributes.CreateAttributeRequest{ + _, err = client.Attributes.CreateAttribute(context.Background(), &attributes.CreateAttributeRequest{ Name: "cards", NamespaceId: exampleNamespace.GetId(), Rule: *policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY.Enum(), @@ -238,7 +250,7 @@ func (s *RoundtripSuite) CreateTestData() error { slog.Info("##################################\n#######################################") - allAttr, err := sdk.Attributes.ListAttributes(context.Background(), &attributes.ListAttributesRequest{}) + allAttr, err := client.Attributes.ListAttributes(context.Background(), &attributes.ListAttributesRequest{}) if err != nil { slog.Error("could not list attributes", slog.String("error", err.Error())) return err @@ -249,7 +261,7 @@ func (s *RoundtripSuite) CreateTestData() error { // get the attribute ids for the values were mapping to the client var attributeValueIDs []string - fqnResp, err := sdk.Attributes.GetAttributeValuesByFqns(context.Background(), &attributes.GetAttributeValuesByFqnsRequest{ + fqnResp, err := client.Attributes.GetAttributeValuesByFqns(context.Background(), &attributes.GetAttributeValuesByFqnsRequest{ Fqns: attributesToMap, WithValue: &policy.AttributeValueSelector{}, }) @@ -264,11 +276,12 @@ func (s *RoundtripSuite) CreateTestData() error { // create subject mappings slog.Info("creating subject mappings for client " + s.TestConfig.ClientID) for _, attributeID := range attributeValueIDs { - _, err = sdk.SubjectMapping.CreateSubjectMapping(context.Background(), &subjectmapping.CreateSubjectMappingRequest{ + _, err = client.SubjectMapping.CreateSubjectMapping(context.Background(), &subjectmapping.CreateSubjectMappingRequest{ AttributeValueId: attributeID, - Actions: []*policy.Action{{Value: &policy.Action_Standard{ - Standard: policy.Action_STANDARD_ACTION_DECRYPT, - }}, + Actions: []*policy.Action{ + {Value: &policy.Action_Standard{ + Standard: policy.Action_STANDARD_ACTION_DECRYPT, + }}, {Value: &policy.Action_Standard{ Standard: policy.Action_STANDARD_ACTION_TRANSMIT, }}, @@ -276,11 +289,12 @@ func (s *RoundtripSuite) CreateTestData() error { NewSubjectConditionSet: &subjectmapping.SubjectConditionSetCreate{ SubjectSets: []*policy.SubjectSet{ {ConditionGroups: []*policy.ConditionGroup{ - {Conditions: []*policy.Condition{{ - SubjectExternalSelectorValue: ".clientId", - Operator: policy.SubjectMappingOperatorEnum_SUBJECT_MAPPING_OPERATOR_ENUM_IN, - SubjectExternalValues: []string{s.TestConfig.ClientID}, - }}, + { + Conditions: []*policy.Condition{{ + SubjectExternalSelectorValue: ".clientId", + Operator: policy.SubjectMappingOperatorEnum_SUBJECT_MAPPING_OPERATOR_ENUM_IN, + SubjectExternalValues: []string{s.TestConfig.ClientID}, + }}, BooleanOperator: policy.ConditionBooleanTypeEnum_CONDITION_BOOLEAN_TYPE_ENUM_AND, }, }}, @@ -299,7 +313,7 @@ func (s *RoundtripSuite) CreateTestData() error { } } - allSubMaps, err := sdk.SubjectMapping.ListSubjectMappings(context.Background(), &subjectmapping.ListSubjectMappingsRequest{}) + allSubMaps, err := client.SubjectMapping.ListSubjectMappings(context.Background(), &subjectmapping.ListSubjectMappingsRequest{}) if err != nil { slog.Error("could not list subject mappings", slog.String("error", err.Error())) return err @@ -309,20 +323,9 @@ func (s *RoundtripSuite) CreateTestData() error { return nil } -func encrypt(testConfig *TestConfig, plaintext string, attributes []string, filename string) error { +func encrypt(client *sdk.SDK, testConfig TestConfig, plaintext string, attributes []string, filename string) error { strReader := strings.NewReader(plaintext) - // Create new offline client - client, err := sdk.New(testConfig.PlatformEndpoint, - sdk.WithInsecurePlaintextConn(), - sdk.WithClientCredentials(testConfig.ClientID, - testConfig.ClientSecret, nil), - sdk.WithTokenEndpoint(testConfig.TokenEndpoint), - ) - if err != nil { - return err - } - tdfFile, err := os.Create(filename) if err != nil { return err @@ -344,17 +347,7 @@ func encrypt(testConfig *TestConfig, plaintext string, attributes []string, file return nil } -func decrypt(testConfig *TestConfig, tdfFile string, plaintext string) error { - // Create new client - client, err := sdk.New(testConfig.PlatformEndpoint, - sdk.WithInsecurePlaintextConn(), - sdk.WithClientCredentials(testConfig.ClientID, - testConfig.ClientSecret, nil), - sdk.WithTokenEndpoint(testConfig.TokenEndpoint), - ) - if err != nil { - return err - } +func decrypt(client *sdk.SDK, tdfFile string, plaintext string) error { file, err := os.Open(tdfFile) if err != nil { return err diff --git a/test/policy-service.bats b/test/policy-service.bats index f08c042f1..47908d0c1 100755 --- a/test/policy-service.bats +++ b/test/policy-service.bats @@ -3,7 +3,7 @@ # Tests for policy service administration @test "gRPC: lists attributes" { - run grpcurl -plaintext "localhost:8080" list + run grpcurl "localhost:8080" list echo "$output" [ $status = 0 ] [[ $output = *grpc.health.v1.Health* ]] diff --git a/test/rego/custom-entity.bats b/test/rego/custom-entity.bats index 4a3506151..0b16ab700 100755 --- a/test/rego/custom-entity.bats +++ b/test/rego/custom-entity.bats @@ -46,9 +46,9 @@ EOF echo "Using Access Token: $ACCESS_TOKEN" # Print the grpcurl command for debugging - echo grpcurl -plaintext -H \"Authorization: Bearer $ACCESS_TOKEN\" -d \"$JSON_BODY\" $BASE_URL authorization.AuthorizationService/GetEntitlements + echo grpcurl -H \"Authorization: Bearer $ACCESS_TOKEN\" -d \"$JSON_BODY\" $BASE_URL authorization.AuthorizationService/GetEntitlements - run grpcurl -plaintext -H "Authorization: Bearer $ACCESS_TOKEN" \ + run grpcurl -H "Authorization: Bearer $ACCESS_TOKEN" \ -d "$JSON_BODY" \ $BASE_URL authorization.AuthorizationService/GetEntitlements diff --git a/test/rego/static-entitlements.bats b/test/rego/static-entitlements.bats index 96042d000..cbb80a028 100755 --- a/test/rego/static-entitlements.bats +++ b/test/rego/static-entitlements.bats @@ -46,9 +46,9 @@ EOF echo "Using Access Token: $ACCESS_TOKEN" # Print the grpcurl command for debugging - echo grpcurl -plaintext -H \"Authorization: Bearer $ACCESS_TOKEN\" -d \"$JSON_BODY\" $BASE_URL authorization.AuthorizationService/GetEntitlements + echo grpcurl -H \"Authorization: Bearer $ACCESS_TOKEN\" -d \"$JSON_BODY\" $BASE_URL authorization.AuthorizationService/GetEntitlements - run grpcurl -plaintext -H "Authorization: Bearer $ACCESS_TOKEN" \ + run grpcurl -H "Authorization: Bearer $ACCESS_TOKEN" \ -d "$JSON_BODY" \ $BASE_URL authorization.AuthorizationService/GetEntitlements diff --git a/test/service-start.bats b/test/service-start.bats index 44febc431..7cc470e37 100755 --- a/test/service-start.bats +++ b/test/service-start.bats @@ -3,14 +3,14 @@ # Tests for validating that the system is nominally running @test "gRPC: health check is healthy" { - run grpcurl -plaintext "localhost:8080" "grpc.health.v1.Health.Check" + run grpcurl "localhost:8080" "grpc.health.v1.Health.Check" echo "$output" [ $status = 0 ] [ $(jq -r .status <<<"${output}") = SERVING ] } @test "gRPC: reports a public key" { - run grpcurl -plaintext "localhost:8080" "kas.AccessService/PublicKey" + run grpcurl "localhost:8080" "kas.AccessService/PublicKey" echo "$output" # Is public key @@ -25,7 +25,7 @@ } @test "REST: new public key endpoint (no algorithm)" { - run curl -s --show-error --fail-with-body --insecure "localhost:8080/kas/v2/kas_public_key" + run curl -s --show-error --fail-with-body "https://localhost:8080/kas/v2/kas_public_key" echo "output=$output" p=$(jq -r .publicKey <<<"${output}") @@ -40,7 +40,7 @@ } @test "REST: new public key endpoint (ec)" { - run curl -s --show-error --fail-with-body --insecure "localhost:8080/kas/v2/kas_public_key?algorithm=ec:secp256r1" + run curl -s --show-error --fail-with-body "https://localhost:8080/kas/v2/kas_public_key?algorithm=ec:secp256r1" echo "$output" # Is public key @@ -55,13 +55,13 @@ } @test "REST: public key endpoint (unknown algorithm)" { - run curl -o /dev/null -s -w "%{http_code}" "localhost:8080/kas/v2/kas_public_key?algorithm=invalid" + run curl -o /dev/null -s -w "%{http_code}" "https://localhost:8080/kas/v2/kas_public_key?algorithm=invalid" echo "$output" [ $output = 404 ] } @test "gRPC: public key endpoint (unknown algorithm)" { - run grpcurl -d '{"algorithm":"invalid"}' -plaintext "localhost:8080" "kas.AccessService/PublicKey" + run grpcurl -d '{"algorithm":"invalid"}' "localhost:8080" "kas.AccessService/PublicKey" echo "$output" [[ $output = *NotFound* ]] } diff --git a/test/tdf-roundtrips.bats b/test/tdf-roundtrips.bats index af28f751f..d9f9ef12b 100755 --- a/test/tdf-roundtrips.bats +++ b/test/tdf-roundtrips.bats @@ -70,7 +70,7 @@ @test "examples: legacy key support Z-TDF" { echo "[INFO] validating default key is r1" - [ $(grpcurl -plaintext "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r1 ] + [ $(grpcurl "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r1 ] echo "[INFO] encrypting samples" go run ./examples encrypt --autoconfigure=false -o sensitive-with-no-kid.txt.tdf --no-kid-in-kao "Hello Legacy" @@ -86,7 +86,7 @@ wait_for_green echo "[INFO] validating default key is r2" - [ $(grpcurl -plaintext "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r2 ] + [ $(grpcurl "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r2 ] echo "[INFO] decrypting after key rotation" go run ./examples decrypt sensitive-with-no-kid.txt.tdf | grep "Hello Legacy" @@ -95,7 +95,7 @@ @test "examples: legacy kas and service config format support" { echo "[INFO] validating default key is r1" - [ $(grpcurl -plaintext "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r1 ] + [ $(grpcurl "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r1 ] echo "[INFO] encrypting samples" go run ./examples encrypt --autoconfigure=false -o sensitive-with-no-kid.txt.tdf --no-kid-in-kao "Hello Legacy" @@ -111,7 +111,7 @@ wait_for_green echo "[INFO] validating default key is r1" - [ $(grpcurl -plaintext "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r1 ] + [ $(grpcurl "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r1 ] echo "[INFO] decrypting after key rotation" go run ./examples decrypt sensitive-with-no-kid.txt.tdf | grep "Hello Legacy" @@ -122,7 +122,7 @@ wait_for_green() { limit=5 for i in $(seq 1 $limit); do - if [ $(grpcurl -plaintext "localhost:8080" "grpc.health.v1.Health.Check" | jq -e -r .status) = SERVING ]; then + if [ $(grpcurl "localhost:8080" "grpc.health.v1.Health.Check" | jq -e -r .status) = SERVING ]; then return 0 fi sleep 4 @@ -158,6 +158,10 @@ services: realm: "opentdf" legacykeycloak: true server: + tls: + enabled: true + cert: ./keys/platform.crt + key: ./keys/platform-key.pem auth: enabled: true enforceDPoP: false @@ -229,6 +233,10 @@ services: realm: "opentdf" legacykeycloak: true server: + tls: + enabled: true + cert: ./keys/platform.crt + key: ./keys/platform-key.pem auth: enabled: true enforceDPoP: false