mirror of
https://github.com/openshift/image-registry.git
synced 2026-02-05 09:45:55 +01:00
the users api is specific to openshift, and is not available on every openshift cluster, i.e when OIDC is configured with external users.
575 lines
22 KiB
Go
575 lines
22 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/distribution/distribution/v3"
|
|
dockercfg "github.com/distribution/distribution/v3/configuration"
|
|
dcontext "github.com/distribution/distribution/v3/context"
|
|
"github.com/distribution/distribution/v3/registry/auth"
|
|
|
|
authenticationapi "k8s.io/api/authentication/v1"
|
|
authorizationapi "k8s.io/api/authorization/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
restclient "k8s.io/client-go/rest"
|
|
|
|
"github.com/openshift/image-registry/pkg/dockerregistry/server/client"
|
|
"github.com/openshift/image-registry/pkg/dockerregistry/server/configuration"
|
|
"github.com/openshift/image-registry/pkg/origin-common/clientcmd"
|
|
"github.com/openshift/image-registry/pkg/testutil"
|
|
)
|
|
|
|
var (
|
|
scheme = runtime.NewScheme()
|
|
codecs = serializer.NewCodecFactory(scheme)
|
|
)
|
|
|
|
func init() {
|
|
authorizationapi.AddToScheme(scheme)
|
|
authenticationapi.AddToScheme(scheme)
|
|
}
|
|
|
|
func sarResponse(ns string, allowed bool, reason string) *authorizationapi.SelfSubjectAccessReview {
|
|
resp := &authorizationapi.SelfSubjectAccessReview{}
|
|
resp.Namespace = ns
|
|
resp.Status = authorizationapi.SubjectAccessReviewStatus{Allowed: allowed, Reason: reason}
|
|
return resp
|
|
}
|
|
|
|
// TestVerifyImageStreamAccess mocks openshift http request/response and
|
|
// tests invalid/valid/scoped openshift tokens.
|
|
func TestVerifyImageStreamAccess(t *testing.T) {
|
|
tests := []struct {
|
|
openshiftResponse response
|
|
expectedError error
|
|
}{
|
|
{
|
|
// Test invalid openshift bearer token
|
|
openshiftResponse: response{401, "Unauthorized"},
|
|
expectedError: ErrOpenShiftAccessDenied,
|
|
},
|
|
{
|
|
// Test valid openshift bearer token but token *not* scoped for create operation
|
|
openshiftResponse: response{
|
|
200,
|
|
runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("foo", false, "not authorized!")),
|
|
},
|
|
expectedError: ErrOpenShiftAccessDenied,
|
|
},
|
|
{
|
|
// Test valid openshift bearer token and token scoped for create operation
|
|
openshiftResponse: response{
|
|
200,
|
|
runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("foo", true, "authorized!")),
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
ctx := context.Background()
|
|
ctx = testutil.WithTestLogger(ctx, t)
|
|
server, _ := simulateOpenShiftMaster([]response{test.openshiftResponse})
|
|
|
|
cfg := clientcmd.NewConfig()
|
|
cfg.SkipEnv = true
|
|
cfg.KubernetesAddr.Set(server.URL)
|
|
cfg.CommonConfig = restclient.Config{
|
|
BearerToken: "magic bearer token",
|
|
Host: server.URL,
|
|
}
|
|
osclient, err := client.NewRegistryClient(cfg).Client()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = verifyImageStreamAccess(ctx, "foo", "bar", "create", osclient, osclient)
|
|
if err == nil || test.expectedError == nil {
|
|
if err != test.expectedError {
|
|
t.Fatalf("verifyImageStreamAccess did not get expected error - got %s - expected %s", err, test.expectedError)
|
|
}
|
|
} else if err.Error() != test.expectedError.Error() {
|
|
t.Fatalf("verifyImageStreamAccess did not get expected error - got %s - expected %s", err, test.expectedError)
|
|
}
|
|
server.Close()
|
|
}
|
|
}
|
|
|
|
// TestAccessController tests complete integration of the v2 registry auth package.
|
|
func TestAccessController(t *testing.T) {
|
|
const addr = "https://openshift-example.com/osapi"
|
|
|
|
authConfig := &configuration.Auth{
|
|
Realm: "myrealm",
|
|
TokenRealm: "http://tokenrealm.com",
|
|
}
|
|
|
|
tests := map[string]struct {
|
|
authConfig *configuration.Auth
|
|
access []auth.Access
|
|
basicToken string
|
|
bearerToken string
|
|
openshiftResponses []response
|
|
expectedError error
|
|
expectedChallenge bool
|
|
expectedHeaders http.Header
|
|
expectedRepoErr string
|
|
expectedActions []string
|
|
}{
|
|
"no token": {
|
|
access: []auth.Access{},
|
|
basicToken: "",
|
|
expectedError: ErrTokenRequired,
|
|
expectedChallenge: true,
|
|
expectedHeaders: http.Header{"Www-Authenticate": []string{`Bearer realm="http://tokenrealm.com/openshift/token"`}},
|
|
},
|
|
"no token, autodetected tokenrealm": {
|
|
authConfig: &configuration.Auth{
|
|
Realm: "myrealm",
|
|
TokenRealm: "",
|
|
},
|
|
access: []auth.Access{},
|
|
basicToken: "",
|
|
expectedError: ErrTokenRequired,
|
|
expectedChallenge: true,
|
|
expectedHeaders: http.Header{"Www-Authenticate": []string{`Bearer realm="https://openshift-example.com/openshift/token"`}},
|
|
},
|
|
"invalid registry token": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{Type: "repository"},
|
|
}},
|
|
basicToken: "ab-cd-ef-gh",
|
|
expectedError: ErrTokenInvalid,
|
|
expectedChallenge: true,
|
|
expectedHeaders: http.Header{"Www-Authenticate": []string{`Basic realm=myrealm,error="failed to decode credentials"`}},
|
|
},
|
|
"invalid openshift basic password": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{Type: "repository"},
|
|
}},
|
|
basicToken: "abcdefgh",
|
|
expectedError: ErrTokenInvalid,
|
|
expectedChallenge: true,
|
|
expectedHeaders: http.Header{"Www-Authenticate": []string{`Basic realm=myrealm,error="failed to decode credentials"`}},
|
|
},
|
|
"valid openshift token but invalid namespace": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: "bar",
|
|
},
|
|
Action: "pull",
|
|
}},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
},
|
|
expectedError: distribution.ErrRepositoryNameInvalid{
|
|
Name: "bar",
|
|
Reason: fmt.Errorf("it must be of the format <project>/<name>"),
|
|
},
|
|
expectedChallenge: false,
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"registry token but does not involve any repository operation": {
|
|
access: []auth.Access{{}},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
},
|
|
expectedError: ErrUnsupportedResource,
|
|
expectedChallenge: false,
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"registry token but does not involve any known action": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: "foo/bar",
|
|
},
|
|
Action: "blah",
|
|
}},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
},
|
|
expectedError: ErrUnsupportedAction,
|
|
expectedChallenge: false,
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"docker login with invalid openshift creds": {
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{{403, ""}},
|
|
expectedError: ErrOpenShiftAccessDenied,
|
|
expectedChallenge: true,
|
|
expectedHeaders: http.Header{"Www-Authenticate": []string{`Basic realm=myrealm,error="access denied"`}},
|
|
expectedActions: []string{"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)"},
|
|
},
|
|
"docker login with valid openshift creds": {
|
|
basicToken: "dXNyMTphd2Vzb21l",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
},
|
|
expectedError: nil,
|
|
expectedChallenge: false,
|
|
expectedActions: []string{"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)"},
|
|
},
|
|
"error running subject access review": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: "foo/bar",
|
|
},
|
|
Action: "pull",
|
|
}},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
{500, "Uh oh"},
|
|
},
|
|
expectedError: errors.New("an error on the server (\"unknown\") has prevented the request from succeeding (post selfsubjectaccessreviews.authorization.k8s.io)"),
|
|
expectedChallenge: false,
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"valid openshift token but token not scoped for the given repo operation": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: "foo/bar",
|
|
},
|
|
Action: "pull",
|
|
}},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("foo", false, "not"))},
|
|
},
|
|
expectedError: ErrOpenShiftAccessDenied,
|
|
expectedChallenge: true,
|
|
expectedHeaders: http.Header{"Www-Authenticate": []string{`Basic realm=myrealm,error="access denied"`}},
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"partially valid openshift token": {
|
|
// Check all the different resource-type/verb combinations we allow to make sure they validate and continue to validate remaining Resource requests
|
|
access: []auth.Access{
|
|
{Resource: auth.Resource{Type: "repository", Name: "foo/aaa"}, Action: "pull"},
|
|
{Resource: auth.Resource{Type: "repository", Name: "bar/bbb"}, Action: "push"},
|
|
{Resource: auth.Resource{Type: "admin"}, Action: "prune"},
|
|
{Resource: auth.Resource{Type: "repository", Name: "baz/ccc"}, Action: "push"},
|
|
},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("foo", true, "authorized!"))},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("bar", true, "authorized!"))},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("", true, "authorized!"))},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("baz", false, "no!"))},
|
|
},
|
|
expectedError: ErrOpenShiftAccessDenied,
|
|
expectedChallenge: true,
|
|
expectedHeaders: http.Header{"Www-Authenticate": []string{`Basic realm=myrealm,error="access denied"`}},
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"deferred cross-mount error": {
|
|
// cross-mount push requests check pull/push access on the target repo and pull access on the source repo.
|
|
// we expect the access check failure for fromrepo/bbb to be added to the context as a deferred error,
|
|
// which our blobstore will look for and prevent a cross mount from.
|
|
access: []auth.Access{
|
|
{Resource: auth.Resource{Type: "repository", Name: "pushrepo/aaa"}, Action: "pull"},
|
|
{Resource: auth.Resource{Type: "repository", Name: "pushrepo/aaa"}, Action: "push"},
|
|
{Resource: auth.Resource{Type: "repository", Name: "fromrepo/bbb"}, Action: "pull"},
|
|
},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("pushrepo", true, "authorized!"))},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("pushrepo", true, "authorized!"))},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("fromrepo", false, "no!"))},
|
|
},
|
|
expectedError: nil,
|
|
expectedChallenge: false,
|
|
expectedRepoErr: "fromrepo/bbb",
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"valid openshift token": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: "foo/bar",
|
|
},
|
|
Action: "pull",
|
|
}},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("", true, "authorized!"))},
|
|
},
|
|
expectedError: nil,
|
|
expectedChallenge: false,
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
"valid anonymous token": {
|
|
access: []auth.Access{{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: "foo/bar",
|
|
},
|
|
Action: "pull",
|
|
}},
|
|
bearerToken: "anonymous",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("foo", true, "authorized!"))},
|
|
},
|
|
expectedError: nil,
|
|
expectedChallenge: false,
|
|
expectedActions: []string{
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=)",
|
|
},
|
|
},
|
|
"pruning": {
|
|
access: []auth.Access{
|
|
{
|
|
Resource: auth.Resource{
|
|
Type: "admin",
|
|
},
|
|
Action: "prune",
|
|
},
|
|
{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: "foo/bar",
|
|
},
|
|
Action: "delete",
|
|
},
|
|
},
|
|
basicToken: "b3BlbnNoaWZ0OmF3ZXNvbWU=",
|
|
openshiftResponses: []response{
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authenticationapi.SchemeGroupVersion), &authenticationapi.SelfSubjectReview{Status: authenticationapi.SelfSubjectReviewStatus{UserInfo: authenticationapi.UserInfo{Username: "usr1"}}})},
|
|
{200, runtime.EncodeOrDie(codecs.LegacyCodec(authorizationapi.SchemeGroupVersion), sarResponse("", true, "authorized!"))},
|
|
},
|
|
expectedError: nil,
|
|
expectedChallenge: false,
|
|
expectedActions: []string{
|
|
"POST /apis/authentication.k8s.io/v1/selfsubjectreviews (Authorization=Bearer awesome)",
|
|
"POST /apis/authorization.k8s.io/v1/selfsubjectaccessreviews (Authorization=Bearer awesome)",
|
|
},
|
|
},
|
|
}
|
|
|
|
for k, test := range tests {
|
|
t.Run(k, func(t *testing.T) {
|
|
reqURL, err := url.Parse(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req, err := http.NewRequest("GET", addr, nil)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", k, err)
|
|
}
|
|
// Simulate a secure request to the specified server
|
|
req.Host = reqURL.Host
|
|
req.TLS = &tls.ConnectionState{ServerName: reqURL.Host}
|
|
if len(test.basicToken) > 0 {
|
|
req.Header.Set("Authorization", fmt.Sprintf("Basic %s", test.basicToken))
|
|
}
|
|
if len(test.bearerToken) > 0 {
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", test.bearerToken))
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ctx = testutil.WithTestLogger(ctx, t)
|
|
|
|
server, actions := simulateOpenShiftMaster(test.openshiftResponses)
|
|
cfg := clientcmd.NewConfig()
|
|
cfg.SkipEnv = true
|
|
cfg.KubernetesAddr.Set(server.URL)
|
|
cfg.CommonConfig = restclient.Config{
|
|
Host: server.URL,
|
|
TLSClientConfig: restclient.TLSClientConfig{Insecure: true},
|
|
}
|
|
config := &dockercfg.Configuration{}
|
|
app := &App{
|
|
ctx: ctx,
|
|
registryClient: client.NewRegistryClient(cfg),
|
|
config: &configuration.Configuration{
|
|
Server: &configuration.Server{
|
|
Addr: "localhost:5000",
|
|
},
|
|
Auth: test.authConfig,
|
|
},
|
|
}
|
|
if app.config.Auth == nil {
|
|
app.config.Auth = authConfig
|
|
}
|
|
if err := configuration.InitExtraConfig(config, app.config); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
accessController, err := app.Auth(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx = dcontext.WithRequest(ctx, req)
|
|
authCtx, err := accessController.Authorized(ctx, test.access...)
|
|
server.Close()
|
|
|
|
expectedActions := test.expectedActions
|
|
if expectedActions == nil {
|
|
expectedActions = []string{}
|
|
}
|
|
if !reflect.DeepEqual(actions, &expectedActions) {
|
|
t.Fatalf("expected: %#v, got: %#v", &expectedActions, actions)
|
|
}
|
|
|
|
if err == nil || test.expectedError == nil {
|
|
if err != test.expectedError {
|
|
t.Fatalf("accessController did not get expected error - got %#+v - expected %v", err, test.expectedError)
|
|
}
|
|
if authCtx == nil {
|
|
t.Fatalf("expected auth context but got nil")
|
|
}
|
|
if !authPerformed(authCtx) {
|
|
t.Fatalf("expected AuthPerformed to be true")
|
|
}
|
|
deferredErrors, hasDeferred := deferredErrorsFrom(authCtx)
|
|
if len(test.expectedRepoErr) > 0 {
|
|
if !hasDeferred || deferredErrors[test.expectedRepoErr] == nil {
|
|
t.Fatalf("expected deferred error for repo %s, got none", test.expectedRepoErr)
|
|
}
|
|
} else {
|
|
if hasDeferred && len(deferredErrors) > 0 {
|
|
t.Fatalf("didn't expect deferred errors, got %#v", deferredErrors)
|
|
}
|
|
}
|
|
} else {
|
|
challengeErr, isChallenge := err.(auth.Challenge)
|
|
if test.expectedChallenge != isChallenge {
|
|
t.Fatalf("expected challenge=%v, accessController returned challenge=%v", test.expectedChallenge, isChallenge)
|
|
}
|
|
if isChallenge {
|
|
recorder := httptest.NewRecorder()
|
|
challengeErr.SetHeaders(req, recorder)
|
|
if !reflect.DeepEqual(recorder.HeaderMap, test.expectedHeaders) {
|
|
t.Fatalf("expected headers %#v, got %#v", test.expectedHeaders, recorder.HeaderMap)
|
|
}
|
|
}
|
|
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Fatalf("accessController did not get expected error - got %+v - expected %s", err, test.expectedError)
|
|
}
|
|
if authCtx != nil {
|
|
t.Fatalf("expected nil auth context but got %s", authCtx)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type response struct {
|
|
code int
|
|
body string
|
|
}
|
|
|
|
func simulateOpenShiftMaster(responses []response) (*httptest.Server, *[]string) {
|
|
i := 0
|
|
actions := []string{}
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
response := response{500, "No response registered"}
|
|
if i < len(responses) {
|
|
response = responses[i]
|
|
}
|
|
i++
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(response.code)
|
|
fmt.Fprintln(w, response.body)
|
|
actions = append(actions, fmt.Sprintf(`%s %s (Authorization=%s)`, r.Method, r.URL.Path, r.Header.Get("Authorization")))
|
|
}))
|
|
return server, &actions
|
|
}
|
|
|
|
func TestSARStatus(t *testing.T) {
|
|
testCases := []struct {
|
|
sar *authorizationapi.SelfSubjectAccessReview
|
|
output string
|
|
}{
|
|
{
|
|
sar: &authorizationapi.SelfSubjectAccessReview{
|
|
Status: authorizationapi.SubjectAccessReviewStatus{},
|
|
},
|
|
output: "no opinion",
|
|
},
|
|
{
|
|
sar: &authorizationapi.SelfSubjectAccessReview{
|
|
Status: authorizationapi.SubjectAccessReviewStatus{
|
|
Allowed: true,
|
|
},
|
|
},
|
|
output: "allowed",
|
|
},
|
|
{
|
|
sar: &authorizationapi.SelfSubjectAccessReview{
|
|
Status: authorizationapi.SubjectAccessReviewStatus{
|
|
Denied: true,
|
|
},
|
|
},
|
|
output: "denied",
|
|
},
|
|
{
|
|
sar: &authorizationapi.SelfSubjectAccessReview{
|
|
Status: authorizationapi.SubjectAccessReviewStatus{
|
|
Denied: true,
|
|
EvaluationError: "webhook failed closed",
|
|
},
|
|
},
|
|
output: "denied: webhook failed closed",
|
|
},
|
|
{
|
|
sar: &authorizationapi.SelfSubjectAccessReview{
|
|
Status: authorizationapi.SubjectAccessReviewStatus{
|
|
Reason: "Error",
|
|
EvaluationError: "no user on request.",
|
|
},
|
|
},
|
|
output: "no opinion (Error): no user on request.",
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
s := sarStatus(tc.sar)
|
|
if s != tc.output {
|
|
t.Errorf("got %q, want %q", s, tc.output)
|
|
}
|
|
}
|
|
}
|