Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/cmd/server/admin/create_nodeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/cmd/server/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
59 changes: 25 additions & 34 deletions pkg/cmd/server/api/latest/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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
}
Expand All @@ -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)
}
6 changes: 4 additions & 2 deletions pkg/cmd/server/api/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ func init() {
Scheme.AddKnownTypes("",
&MasterConfig{},
&NodeConfig{},
&SessionSecrets{},

&IdentityProvider{},
&BasicAuthPasswordIdentityProvider{},
Expand All @@ -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() {}
22 changes: 20 additions & 2 deletions pkg/cmd/server/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions pkg/cmd/server/api/v1/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func init() {
api.Scheme.AddKnownTypes("v1",
&MasterConfig{},
&NodeConfig{},
&SessionSecrets{},

&IdentityProvider{},
&BasicAuthPasswordIdentityProvider{},
Expand All @@ -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() {}
22 changes: 20 additions & 2 deletions pkg/cmd/server/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
65 changes: 63 additions & 2 deletions pkg/cmd/server/api/validation/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should we disallow having no secrets?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because the session store will fail if you don't provide any

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
}
23 changes: 6 additions & 17 deletions pkg/cmd/server/origin/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
})
}
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
Loading