Browse code

OAuth secret config

Allow secrets to be filled via envvar

Default to generating session secrets on master startup

Jordan Liggitt authored on 2015/04/01 22:39:20
Showing 15 changed files
... ...
@@ -380,7 +380,7 @@ func (o CreateNodeConfigOptions) MakeNodeConfig(serverCertFile, serverKeyFile, n
380 380
 		return err
381 381
 	}
382 382
 
383
-	content, err := latestconfigapi.WriteNode(config)
383
+	content, err := latestconfigapi.WriteYAML(config)
384 384
 	if err != nil {
385 385
 		return err
386 386
 	}
... ...
@@ -48,6 +48,11 @@ func GetMasterFileReferences(config *MasterConfig) []*string {
48 48
 	}
49 49
 
50 50
 	if config.OAuthConfig != nil {
51
+
52
+		if config.OAuthConfig.SessionConfig != nil {
53
+			refs = append(refs, &config.OAuthConfig.SessionConfig.SessionSecretsFile)
54
+		}
55
+
51 56
 		for _, identityProvider := range config.OAuthConfig.IdentityProviders {
52 57
 			switch provider := identityProvider.Provider.Object.(type) {
53 58
 			case (*RequestHeaderIdentityProvider):
... ...
@@ -4,25 +4,24 @@ import (
4 4
 	"io/ioutil"
5 5
 	"path"
6 6
 
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
7 8
 	kyaml "github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
8 9
 	configapi "github.com/openshift/origin/pkg/cmd/server/api"
9 10
 
10 11
 	"github.com/ghodss/yaml"
11 12
 )
12 13
 
13
-func ReadMasterConfig(filename string) (*configapi.MasterConfig, error) {
14
-	data, err := ioutil.ReadFile(filename)
15
-	if err != nil {
14
+func ReadSessionSecrets(filename string) (*configapi.SessionSecrets, error) {
15
+	config := &configapi.SessionSecrets{}
16
+	if err := ReadYAMLFile(filename, config); err != nil {
16 17
 		return nil, err
17 18
 	}
19
+	return config, nil
20
+}
18 21
 
22
+func ReadMasterConfig(filename string) (*configapi.MasterConfig, error) {
19 23
 	config := &configapi.MasterConfig{}
20
-	data, err = kyaml.ToJSON(data)
21
-	if err != nil {
22
-		return nil, err
23
-	}
24
-
25
-	if err := Codec.DecodeInto(data, config); err != nil {
24
+	if err := ReadYAMLFile(filename, config); err != nil {
26 25
 		return nil, err
27 26
 	}
28 27
 	return config, nil
... ...
@@ -34,26 +33,18 @@ func ReadAndResolveMasterConfig(filename string) (*configapi.MasterConfig, error
34 34
 		return nil, err
35 35
 	}
36 36
 
37
-	configapi.ResolveMasterConfigPaths(masterConfig, path.Dir(filename))
37
+	if err := configapi.ResolveMasterConfigPaths(masterConfig, path.Dir(filename)); err != nil {
38
+		return nil, err
39
+	}
40
+
38 41
 	return masterConfig, nil
39 42
 }
40 43
 
41 44
 func ReadNodeConfig(filename string) (*configapi.NodeConfig, error) {
42
-	data, err := ioutil.ReadFile(filename)
43
-	if err != nil {
44
-		return nil, err
45
-	}
46
-
47 45
 	config := &configapi.NodeConfig{}
48
-	data, err = kyaml.ToJSON(data)
49
-	if err != nil {
46
+	if err := ReadYAMLFile(filename, config); err != nil {
50 47
 		return nil, err
51 48
 	}
52
-
53
-	if err := Codec.DecodeInto(data, config); err != nil {
54
-		return nil, err
55
-	}
56
-
57 49
 	return config, nil
58 50
 }
59 51
 
... ...
@@ -63,13 +54,15 @@ func ReadAndResolveNodeConfig(filename string) (*configapi.NodeConfig, error) {
63 63
 		return nil, err
64 64
 	}
65 65
 
66
-	configapi.ResolveNodeConfigPaths(nodeConfig, path.Dir(filename))
66
+	if err := configapi.ResolveNodeConfigPaths(nodeConfig, path.Dir(filename)); err != nil {
67
+		return nil, err
68
+	}
69
+
67 70
 	return nodeConfig, nil
68 71
 }
69 72
 
70
-// WriteMaster serializes the config to yaml.
71
-func WriteMaster(config *configapi.MasterConfig) ([]byte, error) {
72
-	json, err := Codec.Encode(config)
73
+func WriteYAML(obj runtime.Object) ([]byte, error) {
74
+	json, err := Codec.Encode(obj)
73 75
 	if err != nil {
74 76
 		return nil, err
75 77
 	}
... ...
@@ -81,16 +74,14 @@ func WriteMaster(config *configapi.MasterConfig) ([]byte, error) {
81 81
 	return content, err
82 82
 }
83 83
 
84
-// WriteNode serializes the config to yaml.
85
-func WriteNode(config *configapi.NodeConfig) ([]byte, error) {
86
-	json, err := Codec.Encode(config)
84
+func ReadYAMLFile(filename string, obj runtime.Object) error {
85
+	data, err := ioutil.ReadFile(filename)
87 86
 	if err != nil {
88
-		return nil, err
87
+		return err
89 88
 	}
90
-
91
-	content, err := yaml.JSONToYAML(json)
89
+	data, err = kyaml.ToJSON(data)
92 90
 	if err != nil {
93
-		return nil, err
91
+		return err
94 92
 	}
95
-	return content, err
93
+	return Codec.DecodeInto(data, obj)
96 94
 }
... ...
@@ -10,6 +10,7 @@ func init() {
10 10
 	Scheme.AddKnownTypes("",
11 11
 		&MasterConfig{},
12 12
 		&NodeConfig{},
13
+		&SessionSecrets{},
13 14
 
14 15
 		&IdentityProvider{},
15 16
 		&BasicAuthPasswordIdentityProvider{},
... ...
@@ -35,5 +36,6 @@ func (*GrantConfig) IsAnAPIObject()                       {}
35 35
 func (*GoogleOAuthProvider) IsAnAPIObject()               {}
36 36
 func (*GitHubOAuthProvider) IsAnAPIObject()               {}
37 37
 
38
-func (*MasterConfig) IsAnAPIObject() {}
39
-func (*NodeConfig) IsAnAPIObject()   {}
38
+func (*MasterConfig) IsAnAPIObject()   {}
39
+func (*NodeConfig) IsAnAPIObject()     {}
40
+func (*SessionSecrets) IsAnAPIObject() {}
... ...
@@ -183,15 +183,33 @@ type TokenConfig struct {
183 183
 	AccessTokenMaxAgeSeconds int32
184 184
 }
185 185
 
186
+// SessionConfig specifies options for cookie-based sessions. Used by AuthRequestHandlerSession
186 187
 type SessionConfig struct {
187
-	// SessionSecrets list the secret(s) to use to encrypt created sessions. Used by AuthRequestHandlerSession
188
-	SessionSecrets []string
188
+	// SessionSecretsFile is a reference to a file containing a serialized SessionSecrets object
189
+	// If no file is specified, a random signing and encryption key are generated at each server start
190
+	SessionSecretsFile string
189 191
 	// SessionMaxAgeSeconds specifies how long created sessions last. Used by AuthRequestHandlerSession
190 192
 	SessionMaxAgeSeconds int32
191 193
 	// SessionName is the cookie name used to store the session
192 194
 	SessionName string
193 195
 }
194 196
 
197
+// SessionSecrets list the secrets to use to sign/encrypt and authenticate/decrypt created sessions.
198
+type SessionSecrets struct {
199
+	api.TypeMeta
200
+
201
+	// New sessions are signed and encrypted using the first secret.
202
+	// Existing sessions are decrypted/authenticated by each secret until one succeeds. This allows rotating secrets.
203
+	Secrets []SessionSecret
204
+}
205
+
206
+type SessionSecret struct {
207
+	// Signing secret, used to authenticate sessions using HMAC. Recommended to use a secret with 32 or 64 bytes.
208
+	Authentication string
209
+	// Encrypting secret, used to encrypt sessions. Must be 16, 24, or 32 characters long, to select AES-128, AES-192, or AES-256.
210
+	Encryption string
211
+}
212
+
195 213
 type IdentityProvider struct {
196 214
 	// Name is used to qualify the identities returned by this provider
197 215
 	Name string
... ...
@@ -11,6 +11,7 @@ func init() {
11 11
 	api.Scheme.AddKnownTypes("v1",
12 12
 		&MasterConfig{},
13 13
 		&NodeConfig{},
14
+		&SessionSecrets{},
14 15
 
15 16
 		&IdentityProvider{},
16 17
 		&BasicAuthPasswordIdentityProvider{},
... ...
@@ -36,5 +37,6 @@ func (*GrantConfig) IsAnAPIObject()                       {}
36 36
 func (*GoogleOAuthProvider) IsAnAPIObject()               {}
37 37
 func (*GitHubOAuthProvider) IsAnAPIObject()               {}
38 38
 
39
-func (*MasterConfig) IsAnAPIObject() {}
40
-func (*NodeConfig) IsAnAPIObject()   {}
39
+func (*MasterConfig) IsAnAPIObject()   {}
40
+func (*NodeConfig) IsAnAPIObject()     {}
41
+func (*SessionSecrets) IsAnAPIObject() {}
... ...
@@ -179,15 +179,33 @@ type TokenConfig struct {
179 179
 	AccessTokenMaxAgeSeconds int32 `json:"accessTokenMaxAgeSeconds"`
180 180
 }
181 181
 
182
+// SessionConfig specifies options for cookie-based sessions. Used by AuthRequestHandlerSession
182 183
 type SessionConfig struct {
183
-	// SessionSecrets list the secret(s) to use to encrypt created sessions. Used by AuthRequestHandlerSession
184
-	SessionSecrets []string `json:"sessionSecrets"`
184
+	// SessionSecretsFile is a reference to a file containing a serialized SessionSecrets object
185
+	// If no file is specified, a random signing and encryption key are generated at each server start
186
+	SessionSecretsFile string `json:"sessionSecretsFile"`
185 187
 	// SessionMaxAgeSeconds specifies how long created sessions last. Used by AuthRequestHandlerSession
186 188
 	SessionMaxAgeSeconds int32 `json:"sessionMaxAgeSeconds"`
187 189
 	// SessionName is the cookie name used to store the session
188 190
 	SessionName string `json:"sessionName"`
189 191
 }
190 192
 
193
+// SessionSecrets list the secrets to use to sign/encrypt and authenticate/decrypt created sessions.
194
+type SessionSecrets struct {
195
+	v1beta3.TypeMeta `json:",inline"`
196
+
197
+	// New sessions are signed and encrypted using the first secret.
198
+	// Existing sessions are decrypted/authenticated by each secret until one succeeds. This allows rotating secrets.
199
+	Secrets []SessionSecret `json:"secrets"`
200
+}
201
+
202
+type SessionSecret struct {
203
+	// Signing secret, used to authenticate sessions using HMAC. Recommended to use a secret with 32 or 64 bytes.
204
+	Authentication string `json:"authentication"`
205
+	// Encrypting secret, used to encrypt sessions. Must be 16, 24, or 32 characters long, to select AES-128, AES-
206
+	Encryption string `json:"encryption"`
207
+}
208
+
191 209
 type IdentityProvider struct {
192 210
 	// Name is used to qualify the identities returned by this provider
193 211
 	Name string `json:"name"`
... ...
@@ -2,10 +2,12 @@ package validation
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"strings"
5 6
 
6 7
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
7 8
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
8 9
 	"github.com/openshift/origin/pkg/cmd/server/api"
10
+	"github.com/openshift/origin/pkg/cmd/server/api/latest"
9 11
 	"github.com/openshift/origin/pkg/user/api/validation"
10 12
 )
11 13
 
... ...
@@ -127,12 +129,71 @@ func ValidateGrantConfig(config api.GrantConfig) fielderrors.ValidationErrorList
127 127
 func ValidateSessionConfig(config *api.SessionConfig) fielderrors.ValidationErrorList {
128 128
 	allErrs := fielderrors.ValidationErrorList{}
129 129
 
130
-	if len(config.SessionSecrets) == 0 {
131
-		allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionSecrets"))
130
+	// Validate session secrets file, if specified
131
+	if len(config.SessionSecretsFile) > 0 {
132
+		fileErrs := ValidateFile(config.SessionSecretsFile, "sessionSecretsFile")
133
+		if len(fileErrs) != 0 {
134
+			// Missing file
135
+			allErrs = append(allErrs, fileErrs...)
136
+		} else {
137
+			// Validate file contents
138
+			secrets, err := latest.ReadSessionSecrets(config.SessionSecretsFile)
139
+			if err != nil {
140
+				allErrs = append(allErrs, fielderrors.NewFieldInvalid("sessionSecretsFile", config.SessionSecretsFile, fmt.Sprintf("error reading file: %v", err)))
141
+			} else {
142
+				for _, err := range ValidateSessionSecrets(secrets) {
143
+					allErrs = append(allErrs, fielderrors.NewFieldInvalid("sessionSecretsFile", config.SessionSecretsFile, err.Error()))
144
+				}
145
+			}
146
+		}
132 147
 	}
148
+
133 149
 	if len(config.SessionName) == 0 {
134 150
 		allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionName"))
135 151
 	}
136 152
 
137 153
 	return allErrs
138 154
 }
155
+
156
+func ValidateSessionSecrets(config *api.SessionSecrets) fielderrors.ValidationErrorList {
157
+	allErrs := fielderrors.ValidationErrorList{}
158
+
159
+	if len(config.Secrets) == 0 {
160
+		allErrs = append(allErrs, fielderrors.NewFieldRequired("secrets"))
161
+	}
162
+
163
+	for i, secret := range config.Secrets {
164
+		switch {
165
+		case len(secret.Authentication) == 0:
166
+			allErrs = append(allErrs, fielderrors.NewFieldRequired(fmt.Sprintf("secrets[%d].authentication", i)))
167
+		case len(secret.Authentication) < 32:
168
+			// Don't output current value in error message... we don't want it logged
169
+			allErrs = append(allErrs,
170
+				fielderrors.NewFieldInvalid(
171
+					fmt.Sprintf("secrets[%d].authentpsecretsication", i),
172
+					strings.Repeat("*", len(secret.Authentication)),
173
+					"must be at least 32 characters long",
174
+				),
175
+			)
176
+		}
177
+
178
+		switch len(secret.Encryption) {
179
+		case 0:
180
+			// Require encryption secrets
181
+			allErrs = append(allErrs, fielderrors.NewFieldRequired(fmt.Sprintf("secrets[%d].encryption", i)))
182
+		case 16, 24, 32:
183
+			// Valid lengths
184
+		default:
185
+			// Don't output current value in error message... we don't want it logged
186
+			allErrs = append(allErrs,
187
+				fielderrors.NewFieldInvalid(
188
+					fmt.Sprintf("secrets[%d].encryption", i),
189
+					strings.Repeat("*", len(secret.Encryption)),
190
+					"must be 16, 24, or 32 characters long",
191
+				),
192
+			)
193
+		}
194
+	}
195
+
196
+	return allErrs
197
+}
... ...
@@ -40,7 +40,6 @@ import (
40 40
 	"github.com/openshift/origin/pkg/auth/server/csrf"
41 41
 	"github.com/openshift/origin/pkg/auth/server/grant"
42 42
 	"github.com/openshift/origin/pkg/auth/server/login"
43
-	"github.com/openshift/origin/pkg/auth/server/session"
44 43
 	"github.com/openshift/origin/pkg/auth/server/tokenrequest"
45 44
 	"github.com/openshift/origin/pkg/auth/userregistry/identitymapper"
46 45
 	configapi "github.com/openshift/origin/pkg/cmd/server/api"
... ...
@@ -231,16 +230,6 @@ func getCSRF() csrf.CSRF {
231 231
 	return csrf.NewCookieCSRF("csrf", "/", "", false, false)
232 232
 }
233 233
 
234
-func (c *AuthConfig) getSessionAuth() *session.Authenticator {
235
-	if c.Options.SessionConfig != nil {
236
-		if c.sessionAuth == nil {
237
-			sessionStore := session.NewStore(int(c.Options.SessionConfig.SessionMaxAgeSeconds), c.Options.SessionConfig.SessionSecrets...)
238
-			c.sessionAuth = session.NewAuthenticator(sessionStore, c.Options.SessionConfig.SessionName)
239
-		}
240
-	}
241
-	return c.sessionAuth
242
-}
243
-
244 234
 func (c *AuthConfig) getAuthorizeAuthenticationHandlers(mux cmdutil.Mux) (authenticator.Request, handlers.AuthenticationHandler, osinserver.AuthorizeHandler, error) {
245 235
 	authRequestHandler, err := c.getAuthenticationRequestHandler()
246 236
 	if err != nil {
... ...
@@ -278,10 +267,10 @@ func (c *AuthConfig) getGrantHandler(mux cmdutil.Mux, auth authenticator.Request
278 278
 
279 279
 // getAuthenticationFinalizer returns an authentication finalizer which is called just prior to writing a response to an authorization request
280 280
 func (c *AuthConfig) getAuthenticationFinalizer() osinserver.AuthorizeHandler {
281
-	if c.getSessionAuth() != nil {
281
+	if c.SessionAuth != nil {
282 282
 		// The session needs to know the authorize flow is done so it can invalidate the session
283 283
 		return osinserver.AuthorizeHandlerFunc(func(ar *osin.AuthorizeRequest, w http.ResponseWriter) (bool, error) {
284
-			_ = c.getSessionAuth().InvalidateAuthentication(w, ar.HttpRequest)
284
+			_ = c.SessionAuth.InvalidateAuthentication(w, ar.HttpRequest)
285 285
 			return false, nil
286 286
 		})
287 287
 	}
... ...
@@ -394,8 +383,8 @@ func (c *AuthConfig) getPasswordAuthenticator(identityProvider configapi.Identit
394 394
 func (c *AuthConfig) getAuthenticationSuccessHandler() handlers.AuthenticationSuccessHandler {
395 395
 	successHandlers := handlers.AuthenticationSuccessHandlers{}
396 396
 
397
-	if c.getSessionAuth() != nil {
398
-		successHandlers = append(successHandlers, c.getSessionAuth())
397
+	if c.SessionAuth != nil {
398
+		successHandlers = append(successHandlers, c.SessionAuth)
399 399
 	}
400 400
 
401 401
 	addedRedirectSuccessHandler := false
... ...
@@ -421,8 +410,8 @@ func (c *AuthConfig) getAuthenticationSuccessHandler() handlers.AuthenticationSu
421 421
 func (c *AuthConfig) getAuthenticationRequestHandler() (authenticator.Request, error) {
422 422
 	var authRequestHandlers []authenticator.Request
423 423
 
424
-	if c.getSessionAuth() != nil {
425
-		authRequestHandlers = append(authRequestHandlers, c.getSessionAuth())
424
+	if c.SessionAuth != nil {
425
+		authRequestHandlers = append(authRequestHandlers, c.SessionAuth)
426 426
 	}
427 427
 
428 428
 	for _, identityProvider := range c.Options.IdentityProviders {
... ...
@@ -1,13 +1,17 @@
1 1
 package origin
2 2
 
3 3
 import (
4
+	"crypto/md5"
4 5
 	"crypto/x509"
5 6
 	"fmt"
6 7
 
8
+	"code.google.com/p/go-uuid/uuid"
9
+
7 10
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
8 11
 
9 12
 	"github.com/openshift/origin/pkg/auth/server/session"
10 13
 	configapi "github.com/openshift/origin/pkg/cmd/server/api"
14
+	"github.com/openshift/origin/pkg/cmd/server/api/latest"
11 15
 	"github.com/openshift/origin/pkg/cmd/server/etcd"
12 16
 	identityregistry "github.com/openshift/origin/pkg/user/registry/identity"
13 17
 	identityetcd "github.com/openshift/origin/pkg/user/registry/identity/etcd"
... ...
@@ -26,8 +30,7 @@ type AuthConfig struct {
26 26
 	UserRegistry     userregistry.Registry
27 27
 	IdentityRegistry identityregistry.Registry
28 28
 
29
-	// sessionAuth holds the Authenticator built from the exported Session* options. It should only be accessed via getSessionAuth(), since it is lazily built.
30
-	sessionAuth *session.Authenticator
29
+	SessionAuth *session.Authenticator
31 30
 }
32 31
 
33 32
 func BuildAuthConfig(options configapi.MasterConfig) (*AuthConfig, error) {
... ...
@@ -41,6 +44,15 @@ func BuildAuthConfig(options configapi.MasterConfig) (*AuthConfig, error) {
41 41
 		return nil, err
42 42
 	}
43 43
 
44
+	var sessionAuth *session.Authenticator
45
+	if options.OAuthConfig.SessionConfig != nil {
46
+		auth, err := BuildSessionAuth(options.OAuthConfig.SessionConfig)
47
+		if err != nil {
48
+			return nil, err
49
+		}
50
+		sessionAuth = auth
51
+	}
52
+
44 53
 	// Build the list of valid redirect_uri prefixes for a login using the openshift-web-console client to redirect to
45 54
 	// TODO: allow configuring this
46 55
 	// TODO: remove hard-coding of development UI server
... ...
@@ -60,8 +72,45 @@ func BuildAuthConfig(options configapi.MasterConfig) (*AuthConfig, error) {
60 60
 
61 61
 		IdentityRegistry: identityRegistry,
62 62
 		UserRegistry:     userRegistry,
63
+
64
+		SessionAuth: sessionAuth,
63 65
 	}
64 66
 
65 67
 	return ret, nil
68
+}
69
+
70
+func BuildSessionAuth(config *configapi.SessionConfig) (*session.Authenticator, error) {
71
+	secrets, err := getSessionSecrets(config.SessionSecretsFile)
72
+	if err != nil {
73
+		return nil, err
74
+	}
75
+	sessionStore := session.NewStore(int(config.SessionMaxAgeSeconds), secrets...)
76
+	return session.NewAuthenticator(sessionStore, config.SessionName), nil
77
+}
78
+
79
+func getSessionSecrets(filename string) ([]string, error) {
80
+	// Build secrets list
81
+	secrets := []string{}
82
+
83
+	if len(filename) != 0 {
84
+		sessionSecrets, err := latest.ReadSessionSecrets(filename)
85
+		if err != nil {
86
+			return nil, fmt.Errorf("error reading sessionSecretsFile %s: %v", filename, err)
87
+		}
88
+
89
+		if len(sessionSecrets.Secrets) == 0 {
90
+			return nil, fmt.Errorf("sessionSecretsFile %s contained no secrets", filename)
91
+		}
92
+
93
+		for _, s := range sessionSecrets.Secrets {
94
+			secrets = append(secrets, s.Authentication)
95
+			secrets = append(secrets, s.Encryption)
96
+		}
97
+	} else {
98
+		// Generate random signing and encryption secrets if none are specified in config
99
+		secrets = append(secrets, fmt.Sprintf("%x", md5.Sum([]byte(uuid.NewRandom().String()))))
100
+		secrets = append(secrets, fmt.Sprintf("%x", md5.Sum([]byte(uuid.NewRandom().String()))))
101
+	}
66 102
 
103
+	return secrets, nil
67 104
 }
68 105
new file mode 100644
... ...
@@ -0,0 +1,96 @@
0
+package origin
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"reflect"
6
+	"testing"
7
+
8
+	"github.com/openshift/origin/pkg/cmd/server/api"
9
+	"github.com/openshift/origin/pkg/cmd/server/api/latest"
10
+)
11
+
12
+func TestGetDefaultSessionSecrets(t *testing.T) {
13
+	secrets, err := getSessionSecrets("")
14
+	if err != nil {
15
+		t.Errorf("Unexpected error: %v", err)
16
+	}
17
+	if len(secrets) != 2 {
18
+		t.Errorf("Unexpected 2 secrets, got: %#v", secrets)
19
+	}
20
+}
21
+
22
+func TestGetMissingSessionSecretsFile(t *testing.T) {
23
+	_, err := getSessionSecrets("missing")
24
+	if err == nil {
25
+		t.Errorf("Expected error, got none")
26
+	}
27
+}
28
+
29
+func TestGetInvalidSessionSecretsFile(t *testing.T) {
30
+	tmpfile, err := ioutil.TempFile("", "invalid.yaml")
31
+	if err != nil {
32
+		t.Fatalf("unexpected error: %v", err)
33
+	}
34
+	defer os.Remove(tmpfile.Name())
35
+
36
+	ioutil.WriteFile(tmpfile.Name(), []byte("invalid content"), os.FileMode(0600))
37
+
38
+	_, err = getSessionSecrets(tmpfile.Name())
39
+	if err == nil {
40
+		t.Errorf("Expected error, got none")
41
+	}
42
+}
43
+
44
+func TestGetEmptySessionSecretsFile(t *testing.T) {
45
+	tmpfile, err := ioutil.TempFile("", "empty.yaml")
46
+	if err != nil {
47
+		t.Fatalf("unexpected error: %v", err)
48
+	}
49
+	defer os.Remove(tmpfile.Name())
50
+
51
+	secrets := &api.SessionSecrets{
52
+		Secrets: []api.SessionSecret{},
53
+	}
54
+
55
+	yaml, err := latest.WriteYAML(secrets)
56
+	if err != nil {
57
+		t.Errorf("Unexpected error: %v", err)
58
+	}
59
+	ioutil.WriteFile(tmpfile.Name(), []byte(yaml), os.FileMode(0600))
60
+
61
+	_, err = getSessionSecrets(tmpfile.Name())
62
+	if err == nil {
63
+		t.Errorf("Expected error, got none")
64
+	}
65
+}
66
+
67
+func TestGetValidSessionSecretsFile(t *testing.T) {
68
+	tmpfile, err := ioutil.TempFile("", "valid.yaml")
69
+	if err != nil {
70
+		t.Fatalf("unexpected error: %v", err)
71
+	}
72
+	defer os.Remove(tmpfile.Name())
73
+
74
+	secrets := &api.SessionSecrets{
75
+		Secrets: []api.SessionSecret{
76
+			{Authentication: "a1", Encryption: "e1"},
77
+			{Authentication: "a2", Encryption: "e2"},
78
+		},
79
+	}
80
+	expectedSecrets := []string{"a1", "e1", "a2", "e2"}
81
+
82
+	yaml, err := latest.WriteYAML(secrets)
83
+	if err != nil {
84
+		t.Errorf("Unexpected error: %v", err)
85
+	}
86
+	ioutil.WriteFile(tmpfile.Name(), []byte(yaml), os.FileMode(0600))
87
+
88
+	readSecrets, err := getSessionSecrets(tmpfile.Name())
89
+	if err != nil {
90
+		t.Errorf("Unexpected error: %v", err)
91
+	}
92
+	if !reflect.DeepEqual(readSecrets, expectedSecrets) {
93
+		t.Errorf("Unexpected %v, got %v", expectedSecrets, readSecrets)
94
+	}
95
+}
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"net/url"
7 7
 	"strconv"
8 8
 
9
-	"code.google.com/p/go-uuid/uuid"
10 9
 	"github.com/golang/glog"
11 10
 	"github.com/spf13/pflag"
12 11
 
... ...
@@ -234,7 +233,7 @@ func (args MasterArgs) BuildSerializeableOAuthConfig() (*configapi.OAuthConfig,
234 234
 		},
235 235
 
236 236
 		SessionConfig: &configapi.SessionConfig{
237
-			SessionSecrets:       []string{uuid.NewUUID().String()},
237
+			SessionSecretsFile:   "",
238 238
 			SessionMaxAgeSeconds: 300,
239 239
 			SessionName:          "ssn",
240 240
 		},
... ...
@@ -219,7 +219,7 @@ func (o MasterOptions) RunMaster() error {
219 219
 			return err
220 220
 		}
221 221
 
222
-		content, err := configapilatest.WriteMaster(masterConfig)
222
+		content, err := configapilatest.WriteYAML(masterConfig)
223 223
 		if err != nil {
224 224
 			return err
225 225
 		}
... ...
@@ -306,11 +306,13 @@ func StartMaster(openshiftMasterConfig *configapi.MasterConfig) error {
306 306
 
307 307
 	unprotectedInstallers := []origin.APIInstaller{}
308 308
 
309
-	authConfig, err := origin.BuildAuthConfig(*openshiftMasterConfig)
310
-	if err != nil {
311
-		return err
309
+	if openshiftMasterConfig.OAuthConfig != nil {
310
+		authConfig, err := origin.BuildAuthConfig(*openshiftMasterConfig)
311
+		if err != nil {
312
+			return err
313
+		}
314
+		unprotectedInstallers = append(unprotectedInstallers, authConfig)
312 315
 	}
313
-	unprotectedInstallers = append(unprotectedInstallers, authConfig)
314 316
 
315 317
 	var standaloneAssetConfig *origin.AssetConfig
316 318
 	if openshiftMasterConfig.AssetConfig != nil {
... ...
@@ -183,7 +183,7 @@ func (o NodeOptions) RunNode() error {
183 183
 			return err
184 184
 		}
185 185
 
186
-		content, err := configapilatest.WriteNode(nodeConfig)
186
+		content, err := configapilatest.WriteYAML(nodeConfig)
187 187
 		if err != nil {
188 188
 			return err
189 189
 		}
190 190
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+// +build integration,!no-etcd
1
+
2
+package integration
3
+
4
+import (
5
+	"testing"
6
+
7
+	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
10
+
11
+	"github.com/openshift/origin/pkg/cmd/util/tokencmd"
12
+	testutil "github.com/openshift/origin/test/util"
13
+)
14
+
15
+func TestOAuthDisabled(t *testing.T) {
16
+	// Build master config
17
+	masterOptions, err := testutil.DefaultMasterOptions()
18
+	if err != nil {
19
+		t.Fatalf("unexpected error: %v", err)
20
+	}
21
+
22
+	// Disable OAuth
23
+	masterOptions.OAuthConfig = nil
24
+
25
+	// Start server
26
+	clusterAdminKubeConfig, err := testutil.StartConfiguredMaster(masterOptions)
27
+	if err != nil {
28
+		t.Fatalf("unexpected error: %v", err)
29
+	}
30
+
31
+	client, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig)
32
+	if err != nil {
33
+		t.Fatalf("unexpected error: %v", err)
34
+	}
35
+
36
+	clientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
37
+	if err != nil {
38
+		t.Fatalf("unexpected error: %v", err)
39
+	}
40
+
41
+	// Make sure cert auth still works
42
+	namespaces, err := client.Namespaces().List(labels.Everything(), fields.Everything())
43
+	if err != nil {
44
+		t.Fatalf("Unexpected error %v", err)
45
+	}
46
+	if len(namespaces.Items) == 0 {
47
+		t.Errorf("Expected namespaces, got none")
48
+	}
49
+
50
+	// Use the server and CA info
51
+	anonConfig := kclient.Config{}
52
+	anonConfig.Host = clientConfig.Host
53
+	anonConfig.CAFile = clientConfig.CAFile
54
+	anonConfig.CAData = clientConfig.CAData
55
+
56
+	// Make sure we can't authenticate using OAuth
57
+	if _, err := tokencmd.RequestToken(&anonConfig, nil, "username", "password"); err == nil {
58
+		t.Error("Expected error, got none")
59
+	}
60
+
61
+}