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

feat(build/testing): add authz specific integration tests #3163

Merged
merged 14 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
1 change: 1 addition & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
"fs/gcs",
"import/export",
"authn/sqlite",
"authz/sqlite",
]
steps:
- uses: actions/checkout@v4
Expand Down
219 changes: 187 additions & 32 deletions build/testing/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ var (
"fs/gcs": gcs,
"import/export": importExport,
"authn/sqlite": authn,
"authz/sqlite": authz,
}
)

Expand Down Expand Up @@ -139,24 +140,25 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger
flipt = flipt.
WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true").
WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true").
WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken)
WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken).
WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_METADATA_IS_BOOTSTRAP", "true")
}
{
// K8s auth configuration
flipt = flipt.
WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_KUBERNETES_ENABLED", "true")

var saToken string
// run an OIDC server which exposes a JWKS url using a private key we own
// and generate a JWT to act as our SA token
flipt, saToken, err = serveOIDC(ctx, client, base, flipt)
var priv []byte
// run an OIDC server which exposes a JWKS url and returns
// the associated private key bytes
flipt, priv, err = serveOIDC(ctx, client, base, flipt)
if err != nil {
return err
}

// mount service account token into base on expected k8s sa token path
base = base.WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/token", dagger.ContainerWithNewFileOpts{
Contents: saToken,
base = base.WithNewFile("/var/run/secrets/flipt/k8s.pem", dagger.ContainerWithNewFileOpts{
Contents: string(priv),
})
}
{
Expand All @@ -182,7 +184,7 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger
Bytes: x509.MarshalPKCS1PrivateKey(priv),
})

base = base.WithNewFile("/var/run/secrets/flipt/private.pem", dagger.ContainerWithNewFileOpts{
base = base.WithNewFile("/var/run/secrets/flipt/jwt.pem", dagger.ContainerWithNewFileOpts{
Contents: string(privBytes),
})
}
Expand All @@ -202,9 +204,11 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger
err = g.Wait()

if _, lerr := client.Container().From("alpine:3.16").
WithEnvVariable("UNIQUE", uuid.New().String()).
WithMountedCache("/logs", logs).
WithExec([]string{"cp", "-r", "/logs", "/out"}).
Directory("/out").Export(ctx, "build/logs"); lerr != nil {
Directory("/out").
Export(ctx, "build/logs"); lerr != nil {
log.Println("Error copying logs", lerr)
}

Expand Down Expand Up @@ -642,6 +646,173 @@ func authn(ctx context.Context, _ *dagger.Client, base, flipt *dagger.Container,
return suite(ctx, "authn", base, fliptToTest, conf)
}

func authz(ctx context.Context, _ *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error {
var (
policyPath = "/etc/flipt/authz/policy.rego"
policyData = "/etc/flipt/authz/data.json"
)

// create unique instance for test case
fliptToTest := flipt.
WithEnvVariable("FLIPT_EXPERIMENTAL_AUTHORIZATION_ENABLED", "true").
WithEnvVariable("FLIPT_AUTHORIZATION_REQUIRED", "true").
WithEnvVariable("FLIPT_AUTHORIZATION_POLICY_LOCAL_PATH", policyPath).
WithNewFile(policyPath, dagger.ContainerWithNewFileOpts{
Contents: `package flipt.authz.v1

import data
import rego.v1

default allow = false

allow if {
input.authentication.metadata["is_bootstrap"] == "true"
}

allow if {
some rule in has_rules

permit_string(rule.resource, input.request.resource)
permit_slice(rule.actions, input.request.action)
permit_string(rule.namespace, input.request.namespace)
}

allow if {
some rule in has_rules

permit_string(rule.resource, input.request.resource)
permit_slice(rule.actions, input.request.action)
not rule.namespace
}

has_rules contains rules if {
some role in data.roles
role.name == input.authentication.metadata["io.flipt.auth.role"]
rules := role.rules[_]
}

has_rules contains rules if {
some role in data.roles
role.name == input.authentication.metadata["io.flipt.auth.k8s.serviceaccount.name"]
rules := role.rules[_]
}

permit_string(allowed, _) if {
allowed == "*"
}

permit_string(allowed, requested) if {
allowed == requested
}

permit_slice(allowed, _) if {
allowed[_] = "*"
}

permit_slice(allowed, requested) if {
allowed[_] = requested
}`,
}).
WithEnvVariable("FLIPT_AUTHORIZATION_DATA_LOCAL_PATH", policyData).
WithNewFile(policyData, dagger.ContainerWithNewFileOpts{
Contents: `{
"version": "0.1.0",
"roles": [
{
"name": "admin",
"rules": [
{
"resource": "*",
"actions": [
"*"
]
}
]
},
{
"name": "editor",
"rules": [
{
"resource": "namespace",
"actions": [
"read"
]
},
{
"resource": "authentication",
"actions": [
"read"
]
},
{
"resource": "flag",
"actions": [
"create",
"read",
"update",
"delete"
]
},
{
"resource": "segment",
"actions": [
"create",
"read",
"update",
"delete"
]
}
]
},
{
"name": "viewer",
"rules": [
{
"resource": "*",
"actions": [
"read"
]
}
]
},
{
"name": "default_viewer",
"rules": [
{
"resource": "*",
"actions": [
"read"
],
"namespace": "default"
}
]
},
{
"name": "production_viewer",
"rules": [
{
"resource": "*",
"actions": [
"read"
],
"namespace": "production"
}
]
}
]
}`,
}).
WithEnvVariable("UNIQUE", uuid.New().String()).
WithExec(nil)

// import state into instance before running test
if err := importInto(ctx, base, flipt, fliptToTest, "--address", conf.address, "--token", bootstrapToken); err != nil {
return func() error { return err }
}

return suite(ctx, "authz", base, fliptToTest, conf)
}

func suite(ctx context.Context, dir string, base, flipt *dagger.Container, conf testConfig) func() error {
return func() (err error) {
flags := []string{"--flipt-addr", conf.address, "--flipt-token", bootstrapToken}
Expand Down Expand Up @@ -746,18 +917,18 @@ func signJWT(key crypto.PrivateKey, claims interface{}) string {
// The function generates two JWTs, one for Flipt to identify itself and one which is returned to the caller.
// The caller can use this as the service account token identity to be mounted into the container with the
// client used for running the test and authenticating with Flipt.
func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Container) (*dagger.Container, string, error) {
func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Container) (*dagger.Container, []byte, error) {
priv, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, "", err
return nil, nil, err
}

rsaSigningKey := &bytes.Buffer{}
if err := pem.Encode(rsaSigningKey, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}); err != nil {
return nil, "", err
return nil, nil, err
}

// generate a SA style JWT for identifying the Flipt service
Expand Down Expand Up @@ -799,20 +970,20 @@ func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Containe

caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, "", err
return nil, nil, err
}

caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
return nil, "", err
return nil, nil, err
}

var caCert bytes.Buffer
if err := pem.Encode(&caCert, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
}); err != nil {
return nil, "", err
return nil, nil, err
}

var caPrivKeyPEM bytes.Buffer
Expand Down Expand Up @@ -845,21 +1016,5 @@ func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Containe
dagger.ContainerWithNewFileOpts{Contents: fliptSAToken}).
WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
dagger.ContainerWithNewFileOpts{Contents: caCert.String()}),
// generate a JWT to used to identify the workload communicating with Flipt
// using the private key components of the key pair served by the OIDC server
signJWT(priv, map[string]any{
"exp": time.Now().Add(24 * time.Hour).Unix(),
"iss": "https://discover.svc",
"kubernetes.io": map[string]any{
"namespace": "integration",
"pod": map[string]any{
"name": "integration-test-7d26f049-kdurb",
"uid": "bd8299f9-c50f-4b76-af33-9d8e3ef2b850",
},
"serviceaccount": map[string]any{
"name": "integration-test",
"uid": "4f18914e-f276-44b2-aebd-27db1d8f8def",
},
},
}), nil
rsaSigningKey.Bytes(), nil
}
2 changes: 1 addition & 1 deletion build/testing/integration/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

func API(t *testing.T, ctx context.Context, opts integration.TestOpts) {
var (
client = opts.DefaultClient(t)
client = opts.TokenClient(t)
protocol = opts.Protocol()
)

Expand Down
Loading
Loading