Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Owl 🦉: More validation and GCP-SM prototype #693

Merged
merged 14 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.23.2
// replace github.com/stateful/godotenv => ../godotenv

require (
cloud.google.com/go/secretmanager v1.14.1
github.com/Masterminds/semver/v3 v3.3.0
github.com/Microsoft/go-winio v0.6.2
github.com/atotto/clipboard v0.1.4
Expand Down Expand Up @@ -52,6 +53,10 @@ require (
)

require (
cloud.google.com/go/auth v0.9.4 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.2 // indirect
cloud.google.com/go/iam v1.2.1 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
Expand All @@ -71,19 +76,26 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/time v0.7.0 // indirect
google.golang.org/api v0.196.0 // indirect
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
gotest.tools/v3 v3.5.1 // indirect
)

Expand Down
111 changes: 104 additions & 7 deletions go.sum

Large diffs are not rendered by default.

134 changes: 130 additions & 4 deletions internal/owl/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ package owl

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"log"
"strings"

sm "cloud.google.com/go/secretmanager/apiv1"
smpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
exprlang "github.com/expr-lang/expr"
"github.com/graphql-go/graphql"
"github.com/pkg/errors"
)

// Constants representing different spec names.
Expand All @@ -34,6 +39,7 @@ var (

var EnvironmentType,
ValidateType,
ResolveType,
RenderType,
SpecTypeErrorsType *graphql.Object

Expand Down Expand Up @@ -248,6 +254,7 @@ func specResolver(mutator SpecResolverMutator) graphql.FieldResolveFn {

spec.Spec.Complex = complexName
spec.Spec.Namespace = complexNs
spec.Spec.Checked = true

// skip if last known status was DELETED
if valOk && val.Value.Status == "DELETED" {
Expand All @@ -272,9 +279,6 @@ func specResolver(mutator SpecResolverMutator) graphql.FieldResolveFn {
}

mutator(val, spec, insecure)
if specOk {
spec.Spec.Checked = true
}
}

return p.Source, nil
Expand Down Expand Up @@ -706,6 +710,122 @@ func init() {
}),
})

ResolveType = graphql.NewObject(graphql.ObjectConfig{
Name: "ResolveType",
Fields: (graphql.FieldsThunk)(func() graphql.Fields {
fields := graphql.Fields{
"transform": &graphql.Field{
Type: ResolveType,
Args: graphql.FieldConfigArgument{
"expr": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var opSet *OperationSet
var complexOpSet *ComplexOperationSet

switch p.Source.(type) {
case *OperationSet:
opSet = p.Source.(*OperationSet)
case *ComplexOperationSet:
complexOpSet = p.Source.(*ComplexOperationSet)
opSet = complexOpSet.OperationSet
default:
return nil, errors.New("source does not contain an OperationSet")
}

expr, ok := p.Args["expr"].(string)
if !ok {
return nil, errors.New("transform without expr")
}

ctx := context.Background()
smClient, err := sm.NewClient(ctx)
if err != nil {
log.Fatalf("failed to setup client: %v", err)
}
defer smClient.Close()

for _, v := range opSet.values {
if v.Value.Status != "UNRESOLVED" {
v.Value.Status = "DELETED"
continue
}

env := map[string]string{"key": v.Var.Key}

program, err := exprlang.Compile(expr, exprlang.Env(env))
if err != nil {
return nil, errors.Wrap(err, "failed to compile transform program")
}

output, err := exprlang.Run(program, env)
if err != nil {
return nil, errors.Wrap(err, "failed to run transform program")
}

res, ok := output.(string)
if !ok {
return nil, errors.New("transform output is not a string")
}

spec, ok := opSet.specs[v.Var.Key]
if !ok {
return nil, fmt.Errorf("missing spec for %s", v.Var.Key)
}

_, aitem, err := complexOpSet.GetAtomicItem(spec)
if err != nil {
return nil, err
}

if aitem.Spec.Name != SpecNameSecret && aitem.Spec.Name != SpecNamePassword {
v.Value.Status = "DELETED"
continue
}

uri := fmt.Sprintf("projects/platform-staging-413816/secrets/%s", res)
// status := strings.ToLower(aitem.Value.Status)
// if aitem.Spec.Name == SpecNameSecret || aitem.Spec.Name == SpecNamePassword {
// _, _ = fmt.Println(status, aitem.Spec.Name, aitem.Var.Key, "via", uri)
// } else {
// _, _ = fmt.Println(status, aitem.Spec.Name, aitem.Var.Key)
// continue
// }

accessRequest := &smpb.AccessSecretVersionRequest{
Name: fmt.Sprintf("%s/versions/latest", uri),
}

result, err := smClient.AccessSecretVersion(ctx, accessRequest)
if err != nil {
return nil, errors.Errorf("failed to access secret version: %v", err)
}

if err := opSet.resolveValue(v.Var.Key, string(result.Payload.Data)); err != nil {
return nil, err
}
}

if complexOpSet != nil {
return complexOpSet, nil
}

return opSet, nil
},
},
"done": &graphql.Field{
Type: EnvironmentType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return p.Source, nil
},
},
}
return fields
}),
})

OperationType := &graphql.Field{
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "VariableOperationType",
Expand Down Expand Up @@ -1024,6 +1144,12 @@ func init() {
return p.Source, nil
},
},
"resolve": &graphql.Field{
Type: ResolveType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return p.Source, nil
},
},
}
}),
})
Expand Down
18 changes: 9 additions & 9 deletions internal/owl/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"gopkg.in/yaml.v3"
)

func Test_Graph(t *testing.T) {
func TestGraph(t *testing.T) {
t.Run("introspect schema", func(t *testing.T) {
result := graphql.Do(graphql.Params{
Schema: Schema,
Expand All @@ -33,7 +33,7 @@ func Test_Graph(t *testing.T) {
})
}

func Test_QuerySpecs(t *testing.T) {
func TestQuerySpecs(t *testing.T) {
t.Run("query list of specs", func(t *testing.T) {
result := graphql.Do(graphql.Params{
Schema: Schema,
Expand Down Expand Up @@ -102,7 +102,7 @@ func (testCases fileTestCases) runAll(t *testing.T) {
}
}

func Test_ResolveEnv(t *testing.T) {
func TestResolveEnv(t *testing.T) {
t.Parallel()

testCases := fileTestCases{
Expand Down Expand Up @@ -163,7 +163,7 @@ func Test_ResolveEnv(t *testing.T) {
testCases.runAll(t)
}

func Test_Graph_Update(t *testing.T) {
func TestGraph_Update(t *testing.T) {
testCases := fileTestCases{
{
name: "Store update",
Expand All @@ -178,7 +178,7 @@ func Test_Graph_Update(t *testing.T) {
testCases.runAll(t)
}

func Test_Graph_Required(t *testing.T) {
func TestGraph_Required(t *testing.T) {
testCases := fileTestCases{
{
name: "Validate simple env",
Expand All @@ -199,7 +199,7 @@ func Test_Graph_Required(t *testing.T) {
testCases.runAll(t)
}

func Test_Graph_Reconcile(t *testing.T) {
func TestGraph_Reconcile(t *testing.T) {
testCases := fileTestCases{
{
name: "Reconcile operationless",
Expand All @@ -220,7 +220,7 @@ func Test_Graph_Reconcile(t *testing.T) {
testCases.runAll(t)
}

func Test_Graph_SensitiveKeys(t *testing.T) {
func TestGraph_SensitiveKeys(t *testing.T) {
testCases := fileTestCases{
{
name: "Sensitive keys",
Expand All @@ -241,7 +241,7 @@ func Test_Graph_SensitiveKeys(t *testing.T) {
testCases.runAll(t)
}

func Test_Graph_Get(t *testing.T) {
func TestGraph_Get(t *testing.T) {
testCases := fileTestCases{
{
name: "InsecureGet",
Expand All @@ -263,7 +263,7 @@ func Test_Graph_Get(t *testing.T) {
testCases.runAll(t)
}

func Test_Graph_DotEnv(t *testing.T) {
func TestGraph_DotEnv(t *testing.T) {
testCases := fileTestCases{
{
name: "Without prefix",
Expand Down
16 changes: 8 additions & 8 deletions internal/owl/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,14 @@ func TestValues(t *testing.T) {
require.Equal(t, map[string]string{
"ALLOWED_URL_PATTERNS": "Plain!",
"API_URL": "Plain!",
"AUTH0_AUDIENCE": "Plain!",
"AUTH0_CLIENT_ID": "Plain!",
"AUTH0_AUDIENCE": "Auth0!",
"AUTH0_CLIENT_ID": "Auth0!",
"AUTH0_COOKIE_DOMAIN": "Plain",
"AUTH0_DEV_ID": "Plain",
"AUTH0_DOMAIN": "Plain!",
"AUTH0_MANAGEMENT_AUDIENCE": "Plain!",
"AUTH0_MANAGEMENT_CLIENT_ID": "Plain!",
"AUTH0_MANAGEMENT_CLIENT_SECRET": "Secret!",
"AUTH0_DOMAIN": "Auth0!",
"AUTH0_MANAGEMENT_AUDIENCE": "Auth0Mgmt!",
"AUTH0_MANAGEMENT_CLIENT_ID": "Auth0Mgmt!",
"AUTH0_MANAGEMENT_CLIENT_SECRET": "Auth0Mgmt!",
"AUTH0_WEBHOOK_TOKEN": "Secret!",
"AUTH_DEV_SKIP_EXP": "Plain",
"CORS_ORIGINS": "Plain",
Expand All @@ -183,8 +183,8 @@ func TestValues(t *testing.T) {
"IDP_REDIRECT_URL": "Plain!",
"MIXPANEL_TOKEN": "Secret",
"NODE_ENV": "Plain",
"OPENAI_API_KEY": "Secret!",
"OPENAI_ORG_ID": "Secret!",
"OPENAI_API_KEY": "OpenAI!",
"OPENAI_ORG_ID": "OpenAI!",
"PORT": "Plain!",
"RATE_LIMIT_MAX": "Plain",
"RATE_LIMIT_TIME_WINDOW": "Plain",
Expand Down
Loading
Loading