diff --git a/pkg/cmd/server/admin/create_nodeconfig.go b/pkg/cmd/server/admin/create_nodeconfig.go index 0e95c53789ee..deda6eccd489 100644 --- a/pkg/cmd/server/admin/create_nodeconfig.go +++ b/pkg/cmd/server/admin/create_nodeconfig.go @@ -380,7 +380,7 @@ func (o CreateNodeConfigOptions) MakeNodeConfig(serverCertFile, serverKeyFile, n return err } - content, err := latestconfigapi.WriteNode(config) + content, err := latestconfigapi.WriteYAML(config) if err != nil { return err } diff --git a/pkg/cmd/server/api/helpers.go b/pkg/cmd/server/api/helpers.go index 86e73716a1d6..75cad15e7035 100644 --- a/pkg/cmd/server/api/helpers.go +++ b/pkg/cmd/server/api/helpers.go @@ -48,6 +48,11 @@ func GetMasterFileReferences(config *MasterConfig) []*string { } if config.OAuthConfig != nil { + + if config.OAuthConfig.SessionConfig != nil { + refs = append(refs, &config.OAuthConfig.SessionConfig.SessionSecretsFile) + } + for _, identityProvider := range config.OAuthConfig.IdentityProviders { switch provider := identityProvider.Provider.Object.(type) { case (*RequestHeaderIdentityProvider): diff --git a/pkg/cmd/server/api/latest/helpers.go b/pkg/cmd/server/api/latest/helpers.go index 9b57e9673625..3e29179dbbd3 100644 --- a/pkg/cmd/server/api/latest/helpers.go +++ b/pkg/cmd/server/api/latest/helpers.go @@ -4,25 +4,24 @@ import ( "io/ioutil" "path" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" kyaml "github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml" configapi "github.com/openshift/origin/pkg/cmd/server/api" "github.com/ghodss/yaml" ) -func ReadMasterConfig(filename string) (*configapi.MasterConfig, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { +func ReadSessionSecrets(filename string) (*configapi.SessionSecrets, error) { + config := &configapi.SessionSecrets{} + if err := ReadYAMLFile(filename, config); err != nil { return nil, err } + return config, nil +} +func ReadMasterConfig(filename string) (*configapi.MasterConfig, error) { config := &configapi.MasterConfig{} - data, err = kyaml.ToJSON(data) - if err != nil { - return nil, err - } - - if err := Codec.DecodeInto(data, config); err != nil { + if err := ReadYAMLFile(filename, config); err != nil { return nil, err } return config, nil @@ -34,26 +33,18 @@ func ReadAndResolveMasterConfig(filename string) (*configapi.MasterConfig, error return nil, err } - configapi.ResolveMasterConfigPaths(masterConfig, path.Dir(filename)) + if err := configapi.ResolveMasterConfigPaths(masterConfig, path.Dir(filename)); err != nil { + return nil, err + } + return masterConfig, nil } func ReadNodeConfig(filename string) (*configapi.NodeConfig, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - config := &configapi.NodeConfig{} - data, err = kyaml.ToJSON(data) - if err != nil { + if err := ReadYAMLFile(filename, config); err != nil { return nil, err } - - if err := Codec.DecodeInto(data, config); err != nil { - return nil, err - } - return config, nil } @@ -63,13 +54,15 @@ func ReadAndResolveNodeConfig(filename string) (*configapi.NodeConfig, error) { return nil, err } - configapi.ResolveNodeConfigPaths(nodeConfig, path.Dir(filename)) + if err := configapi.ResolveNodeConfigPaths(nodeConfig, path.Dir(filename)); err != nil { + return nil, err + } + return nodeConfig, nil } -// WriteMaster serializes the config to yaml. -func WriteMaster(config *configapi.MasterConfig) ([]byte, error) { - json, err := Codec.Encode(config) +func WriteYAML(obj runtime.Object) ([]byte, error) { + json, err := Codec.Encode(obj) if err != nil { return nil, err } @@ -81,16 +74,14 @@ func WriteMaster(config *configapi.MasterConfig) ([]byte, error) { return content, err } -// WriteNode serializes the config to yaml. -func WriteNode(config *configapi.NodeConfig) ([]byte, error) { - json, err := Codec.Encode(config) +func ReadYAMLFile(filename string, obj runtime.Object) error { + data, err := ioutil.ReadFile(filename) if err != nil { - return nil, err + return err } - - content, err := yaml.JSONToYAML(json) + data, err = kyaml.ToJSON(data) if err != nil { - return nil, err + return err } - return content, err + return Codec.DecodeInto(data, obj) } diff --git a/pkg/cmd/server/api/register.go b/pkg/cmd/server/api/register.go index da86dfbb918e..b54805587403 100644 --- a/pkg/cmd/server/api/register.go +++ b/pkg/cmd/server/api/register.go @@ -10,6 +10,7 @@ func init() { Scheme.AddKnownTypes("", &MasterConfig{}, &NodeConfig{}, + &SessionSecrets{}, &IdentityProvider{}, &BasicAuthPasswordIdentityProvider{}, @@ -35,5 +36,6 @@ func (*GrantConfig) IsAnAPIObject() {} func (*GoogleOAuthProvider) IsAnAPIObject() {} func (*GitHubOAuthProvider) IsAnAPIObject() {} -func (*MasterConfig) IsAnAPIObject() {} -func (*NodeConfig) IsAnAPIObject() {} +func (*MasterConfig) IsAnAPIObject() {} +func (*NodeConfig) IsAnAPIObject() {} +func (*SessionSecrets) IsAnAPIObject() {} diff --git a/pkg/cmd/server/api/types.go b/pkg/cmd/server/api/types.go index 78ec686a289d..9b81a5daa45f 100644 --- a/pkg/cmd/server/api/types.go +++ b/pkg/cmd/server/api/types.go @@ -183,15 +183,33 @@ type TokenConfig struct { AccessTokenMaxAgeSeconds int32 } +// SessionConfig specifies options for cookie-based sessions. Used by AuthRequestHandlerSession type SessionConfig struct { - // SessionSecrets list the secret(s) to use to encrypt created sessions. Used by AuthRequestHandlerSession - SessionSecrets []string + // SessionSecretsFile is a reference to a file containing a serialized SessionSecrets object + // If no file is specified, a random signing and encryption key are generated at each server start + SessionSecretsFile string // SessionMaxAgeSeconds specifies how long created sessions last. Used by AuthRequestHandlerSession SessionMaxAgeSeconds int32 // SessionName is the cookie name used to store the session SessionName string } +// SessionSecrets list the secrets to use to sign/encrypt and authenticate/decrypt created sessions. +type SessionSecrets struct { + api.TypeMeta + + // New sessions are signed and encrypted using the first secret. + // Existing sessions are decrypted/authenticated by each secret until one succeeds. This allows rotating secrets. + Secrets []SessionSecret +} + +type SessionSecret struct { + // Signing secret, used to authenticate sessions using HMAC. Recommended to use a secret with 32 or 64 bytes. + Authentication string + // Encrypting secret, used to encrypt sessions. Must be 16, 24, or 32 characters long, to select AES-128, AES-192, or AES-256. + Encryption string +} + type IdentityProvider struct { // Name is used to qualify the identities returned by this provider Name string diff --git a/pkg/cmd/server/api/v1/register.go b/pkg/cmd/server/api/v1/register.go index a4a29c200156..4e8a138756ff 100644 --- a/pkg/cmd/server/api/v1/register.go +++ b/pkg/cmd/server/api/v1/register.go @@ -11,6 +11,7 @@ func init() { api.Scheme.AddKnownTypes("v1", &MasterConfig{}, &NodeConfig{}, + &SessionSecrets{}, &IdentityProvider{}, &BasicAuthPasswordIdentityProvider{}, @@ -36,5 +37,6 @@ func (*GrantConfig) IsAnAPIObject() {} func (*GoogleOAuthProvider) IsAnAPIObject() {} func (*GitHubOAuthProvider) IsAnAPIObject() {} -func (*MasterConfig) IsAnAPIObject() {} -func (*NodeConfig) IsAnAPIObject() {} +func (*MasterConfig) IsAnAPIObject() {} +func (*NodeConfig) IsAnAPIObject() {} +func (*SessionSecrets) IsAnAPIObject() {} diff --git a/pkg/cmd/server/api/v1/types.go b/pkg/cmd/server/api/v1/types.go index ca7f33ffc4d0..2668673aff75 100644 --- a/pkg/cmd/server/api/v1/types.go +++ b/pkg/cmd/server/api/v1/types.go @@ -179,15 +179,33 @@ type TokenConfig struct { AccessTokenMaxAgeSeconds int32 `json:"accessTokenMaxAgeSeconds"` } +// SessionConfig specifies options for cookie-based sessions. Used by AuthRequestHandlerSession type SessionConfig struct { - // SessionSecrets list the secret(s) to use to encrypt created sessions. Used by AuthRequestHandlerSession - SessionSecrets []string `json:"sessionSecrets"` + // SessionSecretsFile is a reference to a file containing a serialized SessionSecrets object + // If no file is specified, a random signing and encryption key are generated at each server start + SessionSecretsFile string `json:"sessionSecretsFile"` // SessionMaxAgeSeconds specifies how long created sessions last. Used by AuthRequestHandlerSession SessionMaxAgeSeconds int32 `json:"sessionMaxAgeSeconds"` // SessionName is the cookie name used to store the session SessionName string `json:"sessionName"` } +// SessionSecrets list the secrets to use to sign/encrypt and authenticate/decrypt created sessions. +type SessionSecrets struct { + v1beta3.TypeMeta `json:",inline"` + + // New sessions are signed and encrypted using the first secret. + // Existing sessions are decrypted/authenticated by each secret until one succeeds. This allows rotating secrets. + Secrets []SessionSecret `json:"secrets"` +} + +type SessionSecret struct { + // Signing secret, used to authenticate sessions using HMAC. Recommended to use a secret with 32 or 64 bytes. + Authentication string `json:"authentication"` + // Encrypting secret, used to encrypt sessions. Must be 16, 24, or 32 characters long, to select AES-128, AES- + Encryption string `json:"encryption"` +} + type IdentityProvider struct { // Name is used to qualify the identities returned by this provider Name string `json:"name"` diff --git a/pkg/cmd/server/api/validation/oauth.go b/pkg/cmd/server/api/validation/oauth.go index 043053665d2d..ecef17c00bf2 100644 --- a/pkg/cmd/server/api/validation/oauth.go +++ b/pkg/cmd/server/api/validation/oauth.go @@ -2,10 +2,12 @@ package validation import ( "fmt" + "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" "github.com/openshift/origin/pkg/cmd/server/api" + "github.com/openshift/origin/pkg/cmd/server/api/latest" "github.com/openshift/origin/pkg/user/api/validation" ) @@ -127,12 +129,71 @@ func ValidateGrantConfig(config api.GrantConfig) fielderrors.ValidationErrorList func ValidateSessionConfig(config *api.SessionConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} - if len(config.SessionSecrets) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionSecrets")) + // Validate session secrets file, if specified + if len(config.SessionSecretsFile) > 0 { + fileErrs := ValidateFile(config.SessionSecretsFile, "sessionSecretsFile") + if len(fileErrs) != 0 { + // Missing file + allErrs = append(allErrs, fileErrs...) + } else { + // Validate file contents + secrets, err := latest.ReadSessionSecrets(config.SessionSecretsFile) + if err != nil { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("sessionSecretsFile", config.SessionSecretsFile, fmt.Sprintf("error reading file: %v", err))) + } else { + for _, err := range ValidateSessionSecrets(secrets) { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("sessionSecretsFile", config.SessionSecretsFile, err.Error())) + } + } + } } + if len(config.SessionName) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionName")) } return allErrs } + +func ValidateSessionSecrets(config *api.SessionSecrets) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(config.Secrets) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("secrets")) + } + + for i, secret := range config.Secrets { + switch { + case len(secret.Authentication) == 0: + allErrs = append(allErrs, fielderrors.NewFieldRequired(fmt.Sprintf("secrets[%d].authentication", i))) + case len(secret.Authentication) < 32: + // Don't output current value in error message... we don't want it logged + allErrs = append(allErrs, + fielderrors.NewFieldInvalid( + fmt.Sprintf("secrets[%d].authentpsecretsication", i), + strings.Repeat("*", len(secret.Authentication)), + "must be at least 32 characters long", + ), + ) + } + + switch len(secret.Encryption) { + case 0: + // Require encryption secrets + allErrs = append(allErrs, fielderrors.NewFieldRequired(fmt.Sprintf("secrets[%d].encryption", i))) + case 16, 24, 32: + // Valid lengths + default: + // Don't output current value in error message... we don't want it logged + allErrs = append(allErrs, + fielderrors.NewFieldInvalid( + fmt.Sprintf("secrets[%d].encryption", i), + strings.Repeat("*", len(secret.Encryption)), + "must be 16, 24, or 32 characters long", + ), + ) + } + } + + return allErrs +} diff --git a/pkg/cmd/server/origin/auth.go b/pkg/cmd/server/origin/auth.go index 98211e0aeac6..895c60449871 100644 --- a/pkg/cmd/server/origin/auth.go +++ b/pkg/cmd/server/origin/auth.go @@ -40,7 +40,6 @@ import ( "github.com/openshift/origin/pkg/auth/server/csrf" "github.com/openshift/origin/pkg/auth/server/grant" "github.com/openshift/origin/pkg/auth/server/login" - "github.com/openshift/origin/pkg/auth/server/session" "github.com/openshift/origin/pkg/auth/server/tokenrequest" "github.com/openshift/origin/pkg/auth/userregistry/identitymapper" configapi "github.com/openshift/origin/pkg/cmd/server/api" @@ -231,16 +230,6 @@ func getCSRF() csrf.CSRF { return csrf.NewCookieCSRF("csrf", "/", "", false, false) } -func (c *AuthConfig) getSessionAuth() *session.Authenticator { - if c.Options.SessionConfig != nil { - if c.sessionAuth == nil { - sessionStore := session.NewStore(int(c.Options.SessionConfig.SessionMaxAgeSeconds), c.Options.SessionConfig.SessionSecrets...) - c.sessionAuth = session.NewAuthenticator(sessionStore, c.Options.SessionConfig.SessionName) - } - } - return c.sessionAuth -} - func (c *AuthConfig) getAuthorizeAuthenticationHandlers(mux cmdutil.Mux) (authenticator.Request, handlers.AuthenticationHandler, osinserver.AuthorizeHandler, error) { authRequestHandler, err := c.getAuthenticationRequestHandler() if err != nil { @@ -278,10 +267,10 @@ func (c *AuthConfig) getGrantHandler(mux cmdutil.Mux, auth authenticator.Request // getAuthenticationFinalizer returns an authentication finalizer which is called just prior to writing a response to an authorization request func (c *AuthConfig) getAuthenticationFinalizer() osinserver.AuthorizeHandler { - if c.getSessionAuth() != nil { + if c.SessionAuth != nil { // The session needs to know the authorize flow is done so it can invalidate the session return osinserver.AuthorizeHandlerFunc(func(ar *osin.AuthorizeRequest, w http.ResponseWriter) (bool, error) { - _ = c.getSessionAuth().InvalidateAuthentication(w, ar.HttpRequest) + _ = c.SessionAuth.InvalidateAuthentication(w, ar.HttpRequest) return false, nil }) } @@ -394,8 +383,8 @@ func (c *AuthConfig) getPasswordAuthenticator(identityProvider configapi.Identit func (c *AuthConfig) getAuthenticationSuccessHandler() handlers.AuthenticationSuccessHandler { successHandlers := handlers.AuthenticationSuccessHandlers{} - if c.getSessionAuth() != nil { - successHandlers = append(successHandlers, c.getSessionAuth()) + if c.SessionAuth != nil { + successHandlers = append(successHandlers, c.SessionAuth) } addedRedirectSuccessHandler := false @@ -421,8 +410,8 @@ func (c *AuthConfig) getAuthenticationSuccessHandler() handlers.AuthenticationSu func (c *AuthConfig) getAuthenticationRequestHandler() (authenticator.Request, error) { var authRequestHandlers []authenticator.Request - if c.getSessionAuth() != nil { - authRequestHandlers = append(authRequestHandlers, c.getSessionAuth()) + if c.SessionAuth != nil { + authRequestHandlers = append(authRequestHandlers, c.SessionAuth) } for _, identityProvider := range c.Options.IdentityProviders { diff --git a/pkg/cmd/server/origin/auth_config.go b/pkg/cmd/server/origin/auth_config.go index 7be5c62597a7..a2e72852547f 100644 --- a/pkg/cmd/server/origin/auth_config.go +++ b/pkg/cmd/server/origin/auth_config.go @@ -1,13 +1,17 @@ package origin import ( + "crypto/md5" "crypto/x509" "fmt" + "code.google.com/p/go-uuid/uuid" + "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/openshift/origin/pkg/auth/server/session" configapi "github.com/openshift/origin/pkg/cmd/server/api" + "github.com/openshift/origin/pkg/cmd/server/api/latest" "github.com/openshift/origin/pkg/cmd/server/etcd" identityregistry "github.com/openshift/origin/pkg/user/registry/identity" identityetcd "github.com/openshift/origin/pkg/user/registry/identity/etcd" @@ -26,8 +30,7 @@ type AuthConfig struct { UserRegistry userregistry.Registry IdentityRegistry identityregistry.Registry - // sessionAuth holds the Authenticator built from the exported Session* options. It should only be accessed via getSessionAuth(), since it is lazily built. - sessionAuth *session.Authenticator + SessionAuth *session.Authenticator } func BuildAuthConfig(options configapi.MasterConfig) (*AuthConfig, error) { @@ -41,6 +44,15 @@ func BuildAuthConfig(options configapi.MasterConfig) (*AuthConfig, error) { return nil, err } + var sessionAuth *session.Authenticator + if options.OAuthConfig.SessionConfig != nil { + auth, err := BuildSessionAuth(options.OAuthConfig.SessionConfig) + if err != nil { + return nil, err + } + sessionAuth = auth + } + // Build the list of valid redirect_uri prefixes for a login using the openshift-web-console client to redirect to // TODO: allow configuring this // TODO: remove hard-coding of development UI server @@ -60,8 +72,45 @@ func BuildAuthConfig(options configapi.MasterConfig) (*AuthConfig, error) { IdentityRegistry: identityRegistry, UserRegistry: userRegistry, + + SessionAuth: sessionAuth, } return ret, nil +} + +func BuildSessionAuth(config *configapi.SessionConfig) (*session.Authenticator, error) { + secrets, err := getSessionSecrets(config.SessionSecretsFile) + if err != nil { + return nil, err + } + sessionStore := session.NewStore(int(config.SessionMaxAgeSeconds), secrets...) + return session.NewAuthenticator(sessionStore, config.SessionName), nil +} + +func getSessionSecrets(filename string) ([]string, error) { + // Build secrets list + secrets := []string{} + + if len(filename) != 0 { + sessionSecrets, err := latest.ReadSessionSecrets(filename) + if err != nil { + return nil, fmt.Errorf("error reading sessionSecretsFile %s: %v", filename, err) + } + + if len(sessionSecrets.Secrets) == 0 { + return nil, fmt.Errorf("sessionSecretsFile %s contained no secrets", filename) + } + + for _, s := range sessionSecrets.Secrets { + secrets = append(secrets, s.Authentication) + secrets = append(secrets, s.Encryption) + } + } else { + // Generate random signing and encryption secrets if none are specified in config + secrets = append(secrets, fmt.Sprintf("%x", md5.Sum([]byte(uuid.NewRandom().String())))) + secrets = append(secrets, fmt.Sprintf("%x", md5.Sum([]byte(uuid.NewRandom().String())))) + } + return secrets, nil } diff --git a/pkg/cmd/server/origin/auth_config_test.go b/pkg/cmd/server/origin/auth_config_test.go new file mode 100644 index 000000000000..b4af6e0cee4d --- /dev/null +++ b/pkg/cmd/server/origin/auth_config_test.go @@ -0,0 +1,96 @@ +package origin + +import ( + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/openshift/origin/pkg/cmd/server/api" + "github.com/openshift/origin/pkg/cmd/server/api/latest" +) + +func TestGetDefaultSessionSecrets(t *testing.T) { + secrets, err := getSessionSecrets("") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(secrets) != 2 { + t.Errorf("Unexpected 2 secrets, got: %#v", secrets) + } +} + +func TestGetMissingSessionSecretsFile(t *testing.T) { + _, err := getSessionSecrets("missing") + if err == nil { + t.Errorf("Expected error, got none") + } +} + +func TestGetInvalidSessionSecretsFile(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "invalid.yaml") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer os.Remove(tmpfile.Name()) + + ioutil.WriteFile(tmpfile.Name(), []byte("invalid content"), os.FileMode(0600)) + + _, err = getSessionSecrets(tmpfile.Name()) + if err == nil { + t.Errorf("Expected error, got none") + } +} + +func TestGetEmptySessionSecretsFile(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "empty.yaml") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer os.Remove(tmpfile.Name()) + + secrets := &api.SessionSecrets{ + Secrets: []api.SessionSecret{}, + } + + yaml, err := latest.WriteYAML(secrets) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + ioutil.WriteFile(tmpfile.Name(), []byte(yaml), os.FileMode(0600)) + + _, err = getSessionSecrets(tmpfile.Name()) + if err == nil { + t.Errorf("Expected error, got none") + } +} + +func TestGetValidSessionSecretsFile(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "valid.yaml") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer os.Remove(tmpfile.Name()) + + secrets := &api.SessionSecrets{ + Secrets: []api.SessionSecret{ + {Authentication: "a1", Encryption: "e1"}, + {Authentication: "a2", Encryption: "e2"}, + }, + } + expectedSecrets := []string{"a1", "e1", "a2", "e2"} + + yaml, err := latest.WriteYAML(secrets) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + ioutil.WriteFile(tmpfile.Name(), []byte(yaml), os.FileMode(0600)) + + readSecrets, err := getSessionSecrets(tmpfile.Name()) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(readSecrets, expectedSecrets) { + t.Errorf("Unexpected %v, got %v", expectedSecrets, readSecrets) + } +} diff --git a/pkg/cmd/server/start/master_args.go b/pkg/cmd/server/start/master_args.go index 8714e3f2a110..20289729e38f 100644 --- a/pkg/cmd/server/start/master_args.go +++ b/pkg/cmd/server/start/master_args.go @@ -6,7 +6,6 @@ import ( "net/url" "strconv" - "code.google.com/p/go-uuid/uuid" "github.com/golang/glog" "github.com/spf13/pflag" @@ -234,7 +233,7 @@ func (args MasterArgs) BuildSerializeableOAuthConfig() (*configapi.OAuthConfig, }, SessionConfig: &configapi.SessionConfig{ - SessionSecrets: []string{uuid.NewUUID().String()}, + SessionSecretsFile: "", SessionMaxAgeSeconds: 300, SessionName: "ssn", }, diff --git a/pkg/cmd/server/start/start_master.go b/pkg/cmd/server/start/start_master.go index 34e07501ea26..9ecc7495cc6c 100644 --- a/pkg/cmd/server/start/start_master.go +++ b/pkg/cmd/server/start/start_master.go @@ -219,7 +219,7 @@ func (o MasterOptions) RunMaster() error { return err } - content, err := configapilatest.WriteMaster(masterConfig) + content, err := configapilatest.WriteYAML(masterConfig) if err != nil { return err } @@ -306,11 +306,13 @@ func StartMaster(openshiftMasterConfig *configapi.MasterConfig) error { unprotectedInstallers := []origin.APIInstaller{} - authConfig, err := origin.BuildAuthConfig(*openshiftMasterConfig) - if err != nil { - return err + if openshiftMasterConfig.OAuthConfig != nil { + authConfig, err := origin.BuildAuthConfig(*openshiftMasterConfig) + if err != nil { + return err + } + unprotectedInstallers = append(unprotectedInstallers, authConfig) } - unprotectedInstallers = append(unprotectedInstallers, authConfig) var standaloneAssetConfig *origin.AssetConfig if openshiftMasterConfig.AssetConfig != nil { diff --git a/pkg/cmd/server/start/start_node.go b/pkg/cmd/server/start/start_node.go index d854821cf8c6..3bea71a66175 100644 --- a/pkg/cmd/server/start/start_node.go +++ b/pkg/cmd/server/start/start_node.go @@ -183,7 +183,7 @@ func (o NodeOptions) RunNode() error { return err } - content, err := configapilatest.WriteNode(nodeConfig) + content, err := configapilatest.WriteYAML(nodeConfig) if err != nil { return err } diff --git a/test/integration/oauth_disabled_test.go b/test/integration/oauth_disabled_test.go new file mode 100644 index 000000000000..32d7d81600ed --- /dev/null +++ b/test/integration/oauth_disabled_test.go @@ -0,0 +1,62 @@ +// +build integration,!no-etcd + +package integration + +import ( + "testing" + + kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + + "github.com/openshift/origin/pkg/cmd/util/tokencmd" + testutil "github.com/openshift/origin/test/util" +) + +func TestOAuthDisabled(t *testing.T) { + // Build master config + masterOptions, err := testutil.DefaultMasterOptions() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Disable OAuth + masterOptions.OAuthConfig = nil + + // Start server + clusterAdminKubeConfig, err := testutil.StartConfiguredMaster(masterOptions) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + client, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + clientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Make sure cert auth still works + namespaces, err := client.Namespaces().List(labels.Everything(), fields.Everything()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if len(namespaces.Items) == 0 { + t.Errorf("Expected namespaces, got none") + } + + // Use the server and CA info + anonConfig := kclient.Config{} + anonConfig.Host = clientConfig.Host + anonConfig.CAFile = clientConfig.CAFile + anonConfig.CAData = clientConfig.CAData + + // Make sure we can't authenticate using OAuth + if _, err := tokencmd.RequestToken(&anonConfig, nil, "username", "password"); err == nil { + t.Error("Expected error, got none") + } + +}