2022-06-08 16:05:19 -04:00
|
|
|
// Copyright 2022 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package externalaccount
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"sort"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
)
|
|
|
|
|
|
|
|
type testEnvironment struct {
|
|
|
|
envVars map[string]string
|
|
|
|
deadline time.Time
|
|
|
|
deadlineSet bool
|
|
|
|
byteResponse []byte
|
|
|
|
jsonResponse *executableResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
var executablesAllowed = map[string]string{
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1",
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testEnvironment) existingEnv() []string {
|
|
|
|
result := []string{}
|
|
|
|
for k, v := range t.envVars {
|
|
|
|
result = append(result, fmt.Sprintf("%v=%v", k, v))
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testEnvironment) getenv(key string) string {
|
|
|
|
return t.envVars[key]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testEnvironment) run(ctx context.Context, command string, env []string) ([]byte, error) {
|
|
|
|
t.deadline, t.deadlineSet = ctx.Deadline()
|
|
|
|
if t.jsonResponse != nil {
|
|
|
|
return json.Marshal(t.jsonResponse)
|
|
|
|
}
|
|
|
|
return t.byteResponse, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testEnvironment) getDeadline() (time.Time, bool) {
|
|
|
|
return t.deadline, t.deadlineSet
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testEnvironment) now() time.Time {
|
|
|
|
return defaultTime
|
|
|
|
}
|
|
|
|
|
|
|
|
func Bool(b bool) *bool {
|
|
|
|
return &b
|
|
|
|
}
|
|
|
|
|
|
|
|
func Int(i int) *int {
|
|
|
|
return &i
|
|
|
|
}
|
|
|
|
|
|
|
|
var creationTests = []struct {
|
|
|
|
name string
|
|
|
|
executableConfig ExecutableConfig
|
|
|
|
expectedErr error
|
|
|
|
expectedTimeout time.Duration
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Basic Creation",
|
|
|
|
executableConfig: ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(50000),
|
|
|
|
},
|
|
|
|
expectedTimeout: 50000 * time.Millisecond,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Without Timeout",
|
|
|
|
executableConfig: ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
},
|
|
|
|
expectedTimeout: 30000 * time.Millisecond,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Without Command",
|
|
|
|
executableConfig: ExecutableConfig{},
|
|
|
|
expectedErr: commandMissingError(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Timeout Too Low",
|
|
|
|
executableConfig: ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(4999),
|
|
|
|
},
|
|
|
|
expectedErr: timeoutRangeError(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Timeout Lower Bound",
|
|
|
|
executableConfig: ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(5000),
|
|
|
|
},
|
|
|
|
expectedTimeout: 5000 * time.Millisecond,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Timeout Upper Bound",
|
|
|
|
executableConfig: ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(120000),
|
|
|
|
},
|
|
|
|
expectedTimeout: 120000 * time.Millisecond,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Timeout Too High",
|
|
|
|
executableConfig: ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(120001),
|
|
|
|
},
|
|
|
|
expectedErr: timeoutRangeError(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateExecutableCredential(t *testing.T) {
|
|
|
|
for _, tt := range creationTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
ecs, err := CreateExecutableCredential(context.Background(), &tt.executableConfig, nil)
|
|
|
|
if tt.expectedErr != nil {
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("Expected error but found none")
|
|
|
|
}
|
|
|
|
if got, want := err.Error(), tt.expectedErr.Error(); got != want {
|
|
|
|
t.Errorf("Incorrect error received.\nReceived: %s\nExpected: %s", got, want)
|
|
|
|
}
|
|
|
|
} else if err != nil {
|
|
|
|
ecJson := "{???}"
|
|
|
|
if ecBytes, err2 := json.Marshal(tt.executableConfig); err2 != nil {
|
|
|
|
ecJson = string(ecBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Fatalf("CreateExecutableCredential with %v returned error: %v", ecJson, err)
|
|
|
|
} else {
|
|
|
|
if ecs.Command != "blarg" {
|
|
|
|
t.Errorf("ecs.Command got %v but want %v", ecs.Command, "blarg")
|
|
|
|
}
|
|
|
|
if ecs.Timeout != tt.expectedTimeout {
|
|
|
|
t.Errorf("ecs.Timeout got %v but want %v", ecs.Timeout, tt.expectedTimeout)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var getEnvironmentTests = []struct {
|
|
|
|
name string
|
|
|
|
config Config
|
|
|
|
environment testEnvironment
|
|
|
|
expectedEnvironment []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Minimal Executable Config",
|
|
|
|
config: Config{
|
|
|
|
Audience: "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/oidc",
|
|
|
|
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
CredentialSource: CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
environment: testEnvironment{
|
|
|
|
envVars: map[string]string{
|
|
|
|
"A": "B",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedEnvironment: []string{
|
|
|
|
"A=B",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/oidc",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Full Impersonation URL",
|
|
|
|
config: Config{
|
|
|
|
Audience: "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/oidc",
|
|
|
|
ServiceAccountImpersonationURL: "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test@project.iam.gserviceaccount.com:generateAccessToken",
|
|
|
|
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
CredentialSource: CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
OutputFile: "/path/to/generated/cached/credentials",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
environment: testEnvironment{
|
|
|
|
envVars: map[string]string{
|
|
|
|
"A": "B",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedEnvironment: []string{
|
|
|
|
"A=B",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/oidc",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL=test@project.iam.gserviceaccount.com",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE=/path/to/generated/cached/credentials",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Impersonation Email",
|
|
|
|
config: Config{
|
|
|
|
Audience: "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/oidc",
|
|
|
|
ServiceAccountImpersonationURL: "test@project.iam.gserviceaccount.com",
|
|
|
|
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
CredentialSource: CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
OutputFile: "/path/to/generated/cached/credentials",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
environment: testEnvironment{
|
|
|
|
envVars: map[string]string{
|
|
|
|
"A": "B",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedEnvironment: []string{
|
|
|
|
"A=B",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/oidc",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0",
|
|
|
|
"GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE=/path/to/generated/cached/credentials",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExecutableCredentialGetEnvironment(t *testing.T) {
|
|
|
|
for _, tt := range getEnvironmentTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
config := tt.config
|
|
|
|
|
|
|
|
ecs, err := CreateExecutableCredential(context.Background(), config.CredentialSource.Executable, &config)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("creation failed %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ecs.env = &tt.environment
|
|
|
|
|
|
|
|
// This Transformer sorts a []string.
|
|
|
|
sorter := cmp.Transformer("Sort", func(in []string) []string {
|
|
|
|
out := append([]string(nil), in...) // Copy input to avoid mutating it
|
|
|
|
sort.Strings(out)
|
|
|
|
return out
|
|
|
|
})
|
|
|
|
|
|
|
|
if got, want := ecs.executableEnvironment(), tt.expectedEnvironment; !cmp.Equal(got, want, sorter) {
|
|
|
|
t.Errorf("Incorrect environment received.\nReceived: %s\nExpected: %s", got, want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var failureTests = []struct {
|
|
|
|
name string
|
|
|
|
testEnvironment testEnvironment
|
|
|
|
noExecution bool
|
|
|
|
expectedErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Environment Variable Not Set",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
byteResponse: []byte{},
|
|
|
|
},
|
|
|
|
noExecution: true,
|
|
|
|
expectedErr: executablesDisallowedError(),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Invalid Token",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
byteResponse: []byte("tokentokentoken"),
|
|
|
|
},
|
|
|
|
expectedErr: jsonParsingError(executableSource, "tokentokentoken"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Version Field Missing",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(executableSource, "version"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Success Field Missing",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Version: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(executableSource, "success"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "User defined error",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
Code: "404",
|
|
|
|
Message: "Token Not Found",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: userDefinedError("404", "Token Not Found"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "User defined error without code",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
Message: "Token Not Found",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: malformedFailureError(),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "User defined error without message",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
Code: "404",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: malformedFailureError(),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "User defined error without fields",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: malformedFailureError(),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Newer Version",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: unsupportedVersionError(executableSource, 2),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing Token Type",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(executableSource, "token_type"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Token Expired",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() - 1,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: tokenExpiredError(),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Invalid Token Type",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix(),
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:invalid",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: tokenTypeError(executableSource),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing JWT",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix(),
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(executableSource, "id_token"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing ID Token",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix(),
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:id_token",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(executableSource, "id_token"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing SAML Token",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix(),
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:saml2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(executableSource, "saml_response"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetrieveExecutableSubjectTokenExecutableErrors(t *testing.T) {
|
|
|
|
cs := CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(5000),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tfc := testFileConfig
|
|
|
|
tfc.CredentialSource = cs
|
|
|
|
|
|
|
|
base, err := tfc.parse(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("parse() failed %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ecs, ok := base.(executableCredentialSource)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("Wrong credential type created.")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range failureTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
ecs.env = &tt.testEnvironment
|
|
|
|
|
|
|
|
if _, err = ecs.subjectToken(); err == nil {
|
|
|
|
t.Fatalf("Expected error but found none")
|
|
|
|
} else if got, want := err.Error(), tt.expectedErr.Error(); got != want {
|
|
|
|
t.Errorf("Incorrect error received.\nReceived: %s\nExpected: %s", got, want)
|
|
|
|
}
|
|
|
|
|
|
|
|
deadline, deadlineSet := tt.testEnvironment.getDeadline()
|
|
|
|
if tt.noExecution {
|
|
|
|
if deadlineSet {
|
|
|
|
t.Errorf("Executable called when it should not have been")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !deadlineSet {
|
|
|
|
t.Errorf("Command run without a deadline")
|
|
|
|
} else if deadline != defaultTime.Add(5*time.Second) {
|
|
|
|
t.Errorf("Command run with incorrect deadline")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var successTests = []struct {
|
|
|
|
name string
|
|
|
|
testEnvironment testEnvironment
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "JWT",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
IdToken: "tokentokentoken",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "ID Token",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:id_token",
|
|
|
|
IdToken: "tokentokentoken",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "SAML",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:saml2",
|
|
|
|
SamlResponse: "tokentokentoken",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-08-05 17:01:37 -04:00
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing Expiration",
|
|
|
|
testEnvironment: testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
IdToken: "tokentokentoken",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-06-08 16:05:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetrieveExecutableSubjectTokenSuccesses(t *testing.T) {
|
|
|
|
cs := CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(5000),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tfc := testFileConfig
|
|
|
|
tfc.CredentialSource = cs
|
|
|
|
|
|
|
|
base, err := tfc.parse(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("parse() failed %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ecs, ok := base.(executableCredentialSource)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("Wrong credential type created.")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range successTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
ecs.env = &tt.testEnvironment
|
|
|
|
|
|
|
|
out, err := ecs.subjectToken()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("retrieveSubjectToken() failed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
deadline, deadlineSet := tt.testEnvironment.getDeadline()
|
|
|
|
if !deadlineSet {
|
|
|
|
t.Errorf("Command run without a deadline")
|
|
|
|
} else if deadline != defaultTime.Add(5*time.Second) {
|
|
|
|
t.Errorf("Command run with incorrect deadline")
|
|
|
|
}
|
|
|
|
|
|
|
|
if got, want := out, "tokentokentoken"; got != want {
|
|
|
|
t.Errorf("Incorrect token received.\nReceived: %s\nExpected: %s", got, want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetrieveOutputFileSubjectTokenNotJSON(t *testing.T) {
|
|
|
|
outputFile, err := ioutil.TempFile("testdata", "result.*.json")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Tempfile failed: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(outputFile.Name())
|
|
|
|
|
|
|
|
cs := CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(5000),
|
|
|
|
OutputFile: outputFile.Name(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tfc := testFileConfig
|
|
|
|
tfc.CredentialSource = cs
|
|
|
|
|
|
|
|
base, err := tfc.parse(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("parse() failed %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ecs, ok := base.(executableCredentialSource)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("Wrong credential type created.")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = outputFile.Write([]byte("tokentokentoken")); err != nil {
|
|
|
|
t.Fatalf("error writing to file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
te := testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
byteResponse: []byte{},
|
|
|
|
}
|
|
|
|
ecs.env = &te
|
|
|
|
|
|
|
|
if _, err = base.subjectToken(); err == nil {
|
|
|
|
t.Fatalf("Expected error but found none")
|
|
|
|
} else if got, want := err.Error(), jsonParsingError(outputFileSource, "tokentokentoken").Error(); got != want {
|
|
|
|
t.Errorf("Incorrect error received.\nExpected: %s\nRecieved: %s", want, got)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, deadlineSet := te.getDeadline()
|
|
|
|
if deadlineSet {
|
|
|
|
t.Errorf("Executable called when it should not have been")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// These are errors in the output file that should be reported to the user.
|
|
|
|
// Most of these will help the developers debug their code.
|
|
|
|
var cacheFailureTests = []struct {
|
|
|
|
name string
|
|
|
|
outputFileContents executableResponse
|
|
|
|
expectedErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Missing Version",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(outputFileSource, "version"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing Success",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Version: 1,
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(outputFileSource, "success"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Newer Version",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 2,
|
|
|
|
},
|
|
|
|
expectedErr: unsupportedVersionError(outputFileSource, 2),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing Token Type",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix(),
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(outputFileSource, "token_type"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing Expiration",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(outputFileSource, "expiration_time"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Invalid Token Type",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix(),
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:invalid",
|
|
|
|
},
|
|
|
|
expectedErr: tokenTypeError(outputFileSource),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing JWT",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(outputFileSource, "id_token"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing ID Token",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:id_token",
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(outputFileSource, "id_token"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Missing SAML",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
},
|
|
|
|
expectedErr: missingFieldError(outputFileSource, "id_token"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetrieveOutputFileSubjectTokenFailureTests(t *testing.T) {
|
|
|
|
for _, tt := range cacheFailureTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
outputFile, err := ioutil.TempFile("testdata", "result.*.json")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Tempfile failed: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(outputFile.Name())
|
|
|
|
|
|
|
|
cs := CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(5000),
|
|
|
|
OutputFile: outputFile.Name(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tfc := testFileConfig
|
|
|
|
tfc.CredentialSource = cs
|
|
|
|
|
|
|
|
base, err := tfc.parse(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("parse() failed %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ecs, ok := base.(executableCredentialSource)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("Wrong credential type created.")
|
|
|
|
}
|
|
|
|
te := testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
byteResponse: []byte{},
|
|
|
|
}
|
|
|
|
ecs.env = &te
|
|
|
|
if err = json.NewEncoder(outputFile).Encode(tt.outputFileContents); err != nil {
|
|
|
|
t.Errorf("Error encoding to file: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err = ecs.subjectToken(); err == nil {
|
|
|
|
t.Errorf("Expected error but found none")
|
|
|
|
} else if got, want := err.Error(), tt.expectedErr.Error(); got != want {
|
|
|
|
t.Errorf("Incorrect error received.\nExpected: %s\nRecieved: %s", want, got)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, deadlineSet := te.getDeadline(); deadlineSet {
|
|
|
|
t.Errorf("Executable called when it should not have been")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// These tests should ignore the error in the output file, and check the executable.
|
|
|
|
var invalidCacheTests = []struct {
|
|
|
|
name string
|
|
|
|
outputFileContents executableResponse
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "User Defined Error",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
Code: "404",
|
|
|
|
Message: "Token Not Found",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "User Defined Error without Code",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
Message: "Token Not Found",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "User Defined Error without Message",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
Code: "404",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "User Defined Error without Fields",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(false),
|
|
|
|
Version: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Expired Token",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() - 1,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetrieveOutputFileSubjectTokenInvalidCache(t *testing.T) {
|
|
|
|
for _, tt := range invalidCacheTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
outputFile, err := ioutil.TempFile("testdata", "result.*.json")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Tempfile failed: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(outputFile.Name())
|
|
|
|
|
|
|
|
cs := CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(5000),
|
|
|
|
OutputFile: outputFile.Name(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tfc := testFileConfig
|
|
|
|
tfc.CredentialSource = cs
|
|
|
|
|
|
|
|
base, err := tfc.parse(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("parse() failed %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
te := testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
jsonResponse: &executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
IdToken: "tokentokentoken",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
ecs, ok := base.(executableCredentialSource)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("Wrong credential type created.")
|
|
|
|
}
|
|
|
|
ecs.env = &te
|
|
|
|
|
|
|
|
if err = json.NewEncoder(outputFile).Encode(tt.outputFileContents); err != nil {
|
|
|
|
t.Errorf("Error encoding to file: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := ecs.subjectToken()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("retrieveSubjectToken() failed: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if deadline, deadlineSet := te.getDeadline(); !deadlineSet {
|
|
|
|
t.Errorf("Command run without a deadline")
|
|
|
|
} else if deadline != defaultTime.Add(5*time.Second) {
|
|
|
|
t.Errorf("Command run with incorrect deadline")
|
|
|
|
}
|
|
|
|
|
|
|
|
if got, want := out, "tokentokentoken"; got != want {
|
|
|
|
t.Errorf("Incorrect token received.\nExpected: %s\nRecieved: %s", want, got)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var cacheSuccessTests = []struct {
|
|
|
|
name string
|
|
|
|
outputFileContents executableResponse
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "JWT",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
|
|
IdToken: "tokentokentoken",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "Id Token",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:id_token",
|
|
|
|
IdToken: "tokentokentoken",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "SAML",
|
|
|
|
outputFileContents: executableResponse{
|
|
|
|
Success: Bool(true),
|
|
|
|
Version: 1,
|
|
|
|
ExpirationTime: defaultTime.Unix() + 3600,
|
|
|
|
TokenType: "urn:ietf:params:oauth:token-type:saml2",
|
|
|
|
SamlResponse: "tokentokentoken",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetrieveOutputFileSubjectTokenJwt(t *testing.T) {
|
|
|
|
for _, tt := range cacheSuccessTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
|
|
|
outputFile, err := ioutil.TempFile("testdata", "result.*.json")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Tempfile failed: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(outputFile.Name())
|
|
|
|
|
|
|
|
cs := CredentialSource{
|
|
|
|
Executable: &ExecutableConfig{
|
|
|
|
Command: "blarg",
|
|
|
|
TimeoutMillis: Int(5000),
|
|
|
|
OutputFile: outputFile.Name(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tfc := testFileConfig
|
|
|
|
tfc.CredentialSource = cs
|
|
|
|
|
|
|
|
base, err := tfc.parse(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("parse() failed %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
te := testEnvironment{
|
|
|
|
envVars: executablesAllowed,
|
|
|
|
byteResponse: []byte{},
|
|
|
|
}
|
|
|
|
|
|
|
|
ecs, ok := base.(executableCredentialSource)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("Wrong credential type created.")
|
|
|
|
}
|
|
|
|
ecs.env = &te
|
|
|
|
|
|
|
|
if err = json.NewEncoder(outputFile).Encode(tt.outputFileContents); err != nil {
|
|
|
|
t.Errorf("Error encoding to file: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if out, err := ecs.subjectToken(); err != nil {
|
|
|
|
t.Errorf("retrieveSubjectToken() failed: %v", err)
|
|
|
|
} else if got, want := out, "tokentokentoken"; got != want {
|
|
|
|
t.Errorf("Incorrect token received.\nExpected: %s\nRecieved: %s", want, got)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, deadlineSet := te.getDeadline(); deadlineSet {
|
|
|
|
t.Errorf("Executable called when it should not have been")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|