Skip to content

Commit 5213e07

Browse files
Merge pull request #18969 from simo5/noauthsrv
Add option to configure an external OAuth server
2 parents 2cf54b9 + 17de8d9 commit 5213e07

File tree

13 files changed

+381
-36
lines changed

13 files changed

+381
-36
lines changed

pkg/authorization/authorizer/scope/converter.go

+13
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,19 @@ func DefaultSupportedScopesMap() map[string]string {
150150
return defaultSupportedScopesMap
151151
}
152152

153+
func DescribeScopes(scopes []string) map[string]string {
154+
ret := map[string]string{}
155+
for _, s := range scopes {
156+
val, ok := defaultSupportedScopesMap[s]
157+
if ok {
158+
ret[s] = val
159+
} else {
160+
ret[s] = ""
161+
}
162+
}
163+
return ret
164+
}
165+
153166
// user:<scope name>
154167
type userEvaluator struct{}
155168

pkg/cmd/server/apis/config/helpers.go

+3
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ func GetMasterFileReferences(config *MasterConfig) []*string {
167167
for k := range config.AuthConfig.WebhookTokenAuthenticators {
168168
refs = append(refs, &config.AuthConfig.WebhookTokenAuthenticators[k].ConfigFile)
169169
}
170+
if len(config.AuthConfig.OAuthMetadataFile) > 0 {
171+
refs = append(refs, &config.AuthConfig.OAuthMetadataFile)
172+
}
170173

171174
refs = append(refs, &config.AggregatorConfig.ProxyClientInfo.CertFile)
172175
refs = append(refs, &config.AggregatorConfig.ProxyClientInfo.KeyFile)

pkg/cmd/server/apis/config/types.go

+6
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ type MasterConfig struct {
381381
EtcdConfig *EtcdConfig
382382
// OAuthConfig, if present start the /oauth endpoint in this process
383383
OAuthConfig *OAuthConfig
384+
384385
// DNSConfig, if present start the DNS server in this process
385386
DNSConfig *DNSConfig
386387

@@ -430,6 +431,11 @@ type MasterAuthConfig struct {
430431
RequestHeader *RequestHeaderAuthenticationOptions
431432
// WebhookTokenAuthnConfig, if present configures remote token reviewers
432433
WebhookTokenAuthenticators []WebhookTokenAuthenticator
434+
// OAuthMetadataFile is a path to a file containing the discovery endpoint for OAuth 2.0 Authorization
435+
// Server Metadata for an external OAuth server.
436+
// See IETF Draft: // https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2
437+
// This option is mutually exclusive with OAuthConfig
438+
OAuthMetadataFile string
433439
}
434440

435441
// RequestHeaderAuthenticationOptions provides options for setting up a front proxy against the entire

pkg/cmd/server/apis/config/v1/testdata/master-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ auditConfig:
2727
webHookKubeConfig: ""
2828
webHookMode: ""
2929
authConfig:
30+
oauthMetadataFile: ""
3031
requestHeader: null
3132
webhookTokenAuthenticators: null
3233
controllerConfig:

pkg/cmd/server/apis/config/v1/types.go

+6
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ type MasterConfig struct {
244244
EtcdConfig *EtcdConfig `json:"etcdConfig"`
245245
// OAuthConfig, if present start the /oauth endpoint in this process
246246
OAuthConfig *OAuthConfig `json:"oauthConfig"`
247+
247248
// DNSConfig, if present start the DNS server in this process
248249
DNSConfig *DNSConfig `json:"dnsConfig"`
249250

@@ -293,6 +294,11 @@ type MasterAuthConfig struct {
293294
RequestHeader *RequestHeaderAuthenticationOptions `json:"requestHeader"`
294295
// WebhookTokenAuthnConfig, if present configures remote token reviewers
295296
WebhookTokenAuthenticators []WebhookTokenAuthenticator `json:"webhookTokenAuthenticators"`
297+
// OAuthMetadataFile is a path to a file containing the discovery endpoint for OAuth 2.0 Authorization
298+
// Server Metadata for an external OAuth server.
299+
// See IETF Draft: // https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2
300+
// This option is mutually exclusive with OAuthConfig
301+
OAuthMetadataFile string `json:"oauthMetadataFile"`
296302
}
297303

298304
// RequestHeaderAuthenticationOptions provides options for setting up a front proxy against the entire

pkg/cmd/server/apis/config/validation/master.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
configapi "github.com/openshift/origin/pkg/cmd/server/apis/config"
2626
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
2727
"github.com/openshift/origin/pkg/cmd/server/cm"
28+
oauthutil "github.com/openshift/origin/pkg/oauth/util"
2829
"github.com/openshift/origin/pkg/security/mcs"
2930
"github.com/openshift/origin/pkg/security/uid"
3031
"github.com/openshift/origin/pkg/util/labelselector"
@@ -141,8 +142,10 @@ func ValidateMasterConfig(config *configapi.MasterConfig, fldPath *field.Path) V
141142
validationResults.AddErrors(ValidatePolicyConfig(config.PolicyConfig, fldPath.Child("policyConfig"))...)
142143
if config.OAuthConfig != nil {
143144
validationResults.Append(ValidateOAuthConfig(config.OAuthConfig, fldPath.Child("oauthConfig")))
145+
if len(config.AuthConfig.OAuthMetadataFile) > 0 {
146+
validationResults.AddErrors(field.Invalid(fldPath.Child("authConfig", "oauthMetadataFile"), config.AuthConfig.OAuthMetadataFile, "Cannot specify external OAuth Metadata when the internal Oauth Server is configured"))
147+
}
144148
}
145-
146149
validationResults.Append(ValidateServiceAccountConfig(config.ServiceAccountConfig, builtInKubernetes, fldPath.Child("serviceAccountConfig")))
147150

148151
validationResults.Append(ValidateHTTPServingInfo(config.ServingInfo, fldPath.Child("servingInfo")))
@@ -171,6 +174,12 @@ func ValidateMasterConfig(config *configapi.MasterConfig, fldPath *field.Path) V
171174
func ValidateMasterAuthConfig(config configapi.MasterAuthConfig, fldPath *field.Path) ValidationResults {
172175
validationResults := ValidationResults{}
173176

177+
if len(config.OAuthMetadataFile) > 0 {
178+
if _, _, err := oauthutil.LoadOAuthMetadataFile(config.OAuthMetadataFile); err != nil {
179+
validationResults.AddErrors(field.Invalid(fldPath.Child("oauthMetadataFile"), config.OAuthMetadataFile, fmt.Sprintf("Metadata validation failed: %v", err)))
180+
}
181+
}
182+
174183
for _, wta := range config.WebhookTokenAuthenticators {
175184
configFile := fldPath.Child("webhookTokenAuthenticators", "ConfigFile")
176185
if len(wta.ConfigFile) == 0 {

pkg/cmd/server/apis/config/validation/master_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package validation
22

33
import (
4+
"fmt"
45
"io/ioutil"
56
"os"
67
"testing"
@@ -535,10 +536,24 @@ func TestValidateMasterAuthConfig(t *testing.T) {
535536
}
536537
defer os.Remove(testConfigFile.Name())
537538

539+
metadataFile, err := ioutil.TempFile("", "oauth.metadata")
540+
if err != nil {
541+
t.Fatalf("unexpected error: %v", err)
542+
}
543+
defer os.Remove(metadataFile.Name())
544+
ioutil.WriteFile(metadataFile.Name(), testMetadataContent, os.FileMode(0644))
545+
badMetadataFile, err := ioutil.TempFile("", "badoauth.metadata")
546+
if err != nil {
547+
t.Fatalf("unexpected error: %v", err)
548+
}
549+
defer os.Remove(badMetadataFile.Name())
550+
ioutil.WriteFile(badMetadataFile.Name(), []byte("bad file"), os.FileMode(0644))
551+
538552
testCases := []struct {
539553
testName string
540554
RequestHeader *configapi.RequestHeaderAuthenticationOptions
541555
WebhookTokenAuthenticators []configapi.WebhookTokenAuthenticator
556+
OAuthMetadataFile string
542557
expectedErrors []string
543558
}{
544559
{
@@ -593,11 +608,22 @@ func TestValidateMasterAuthConfig(t *testing.T) {
593608
"webhookTokenAuthenticators.cacheTTL: Required value",
594609
},
595610
},
611+
{
612+
testName: "No OAuth Metadata file",
613+
OAuthMetadataFile: "NoFile",
614+
expectedErrors: []string{`oauthMetadataFile: Invalid value: "NoFile": Metadata validation failed: Unable to read External OAuth Metadata file: open NoFile: no such file or directory`},
615+
},
616+
{
617+
testName: "Bad Metadata file",
618+
OAuthMetadataFile: badMetadataFile.Name(),
619+
expectedErrors: []string{fmt.Sprintf(`oauthMetadataFile: Invalid value: %q: Metadata validation failed: Unable to decode External OAuth Metadata file: invalid character 'b' looking for beginning of value`, badMetadataFile.Name())},
620+
},
596621
}
597622
for _, test := range testCases {
598623
config := configapi.MasterAuthConfig{
599624
RequestHeader: test.RequestHeader,
600625
WebhookTokenAuthenticators: test.WebhookTokenAuthenticators,
626+
OAuthMetadataFile: test.OAuthMetadataFile,
601627
}
602628
errors := ValidateMasterAuthConfig(config, nil)
603629
if len(test.expectedErrors) != len(errors.Errors) {
@@ -611,3 +637,12 @@ func TestValidateMasterAuthConfig(t *testing.T) {
611637
}
612638
}
613639
}
640+
641+
var testMetadataContent = []byte(`{
642+
"issuer": "https://127.0.0.1/",
643+
"authorization_endpoint": "https://127.0.0.1/",
644+
"token_endpoint": "https://127.0.0.1/",
645+
"scopes_supported": ["openid", "profile", "email", "address", "phone", "offline_access"],
646+
"response_types_supported": ["code", "code token"],
647+
"grant_types_supported": ["authorization_code", "implicit"],
648+
"code_challenge_methods_supported": ["plain", "S256"]}`)

pkg/cmd/server/kubernetes/master/master_config.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -598,23 +598,22 @@ func defaultOpenAPIConfig(config configapi.MasterConfig) *openapicommon.Config {
598598
},
599599
}
600600
}
601-
if config.OAuthConfig != nil {
602-
baseUrl := config.OAuthConfig.MasterPublicURL
601+
if _, oauthMetadata, _ := oauthutil.PrepOauthMetadata(config); oauthMetadata != nil {
603602
securityDefinitions["Oauth2Implicit"] = &spec.SecurityScheme{
604603
SecuritySchemeProps: spec.SecuritySchemeProps{
605604
Type: "oauth2",
606605
Flow: "implicit",
607-
AuthorizationURL: oauthutil.OpenShiftOAuthAuthorizeURL(baseUrl),
608-
Scopes: scope.DefaultSupportedScopesMap(),
606+
AuthorizationURL: oauthMetadata.AuthorizationEndpoint,
607+
Scopes: scope.DescribeScopes(oauthMetadata.ScopesSupported),
609608
},
610609
}
611610
securityDefinitions["Oauth2AccessToken"] = &spec.SecurityScheme{
612611
SecuritySchemeProps: spec.SecuritySchemeProps{
613612
Type: "oauth2",
614613
Flow: "accessCode",
615-
AuthorizationURL: oauthutil.OpenShiftOAuthAuthorizeURL(baseUrl),
616-
TokenURL: oauthutil.OpenShiftOAuthTokenURL(baseUrl),
617-
Scopes: scope.DefaultSupportedScopesMap(),
614+
AuthorizationURL: oauthMetadata.AuthorizationEndpoint,
615+
TokenURL: oauthMetadata.TokenEndpoint,
616+
Scopes: scope.DescribeScopes(oauthMetadata.ScopesSupported),
618617
},
619618
}
620619
}

pkg/cmd/server/origin/master.go

+10-8
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,20 @@ func (c *MasterConfig) newOpenshiftAPIConfig(kubeAPIServerConfig apiserver.Confi
7575
return ret, ret.ExtraConfig.Validate()
7676
}
7777

78-
func (c *MasterConfig) newOpenshiftNonAPIConfig(kubeAPIServerConfig apiserver.Config) *OpenshiftNonAPIConfig {
78+
func (c *MasterConfig) newOpenshiftNonAPIConfig(kubeAPIServerConfig apiserver.Config) (*OpenshiftNonAPIConfig, error) {
79+
var err error
7980
ret := &OpenshiftNonAPIConfig{
8081
GenericConfig: &apiserver.RecommendedConfig{
8182
Config: kubeAPIServerConfig,
8283
SharedInformerFactory: c.ClientGoKubeInformers,
8384
},
84-
ExtraConfig: NonAPIExtraConfig{
85-
EnableOAuth: c.Options.OAuthConfig != nil,
86-
},
8785
}
88-
if c.Options.OAuthConfig != nil {
89-
ret.ExtraConfig.MasterPublicURL = c.Options.OAuthConfig.MasterPublicURL
86+
ret.ExtraConfig.OAuthMetadata, _, err = oauthutil.PrepOauthMetadata(c.Options)
87+
if err != nil {
88+
return nil, err
9089
}
9190

92-
return ret
91+
return ret, nil
9392
}
9493

9594
func (c *MasterConfig) withAPIExtensions(delegateAPIServer apiserver.DelegationTarget, kubeAPIServerConfig apiserver.Config) (apiserver.DelegationTarget, apiextensionsinformers.SharedInformerFactory, error) {
@@ -110,7 +109,10 @@ func (c *MasterConfig) withAPIExtensions(delegateAPIServer apiserver.DelegationT
110109
}
111110

112111
func (c *MasterConfig) withNonAPIRoutes(delegateAPIServer apiserver.DelegationTarget, kubeAPIServerConfig apiserver.Config) (apiserver.DelegationTarget, error) {
113-
openshiftNonAPIConfig := c.newOpenshiftNonAPIConfig(kubeAPIServerConfig)
112+
openshiftNonAPIConfig, err := c.newOpenshiftNonAPIConfig(kubeAPIServerConfig)
113+
if err != nil {
114+
return nil, err
115+
}
114116
openshiftNonAPIServer, err := openshiftNonAPIConfig.Complete().New(delegateAPIServer)
115117
if err != nil {
116118
return nil, err

pkg/cmd/server/origin/nonapiserver.go

+6-18
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
package origin
22

33
import (
4-
"encoding/json"
54
"net/http"
65

7-
"github.com/golang/glog"
8-
96
genericmux "k8s.io/apiserver/pkg/server/mux"
107

11-
oauthutil "github.com/openshift/origin/pkg/oauth/util"
128
genericapiserver "k8s.io/apiserver/pkg/server"
139
)
1410

1511
type NonAPIExtraConfig struct {
16-
MasterPublicURL string
17-
EnableOAuth bool
12+
OAuthMetadata []byte
1813
}
1914

2015
type OpenshiftNonAPIConfig struct {
@@ -59,8 +54,8 @@ func (c completedOpenshiftNonAPIConfig) New(delegationTarget genericapiserver.De
5954

6055
// TODO move this up to the spot where we wire the oauth endpoint
6156
// Set up OAuth metadata only if we are configured to use OAuth
62-
if c.ExtraConfig.EnableOAuth {
63-
initOAuthAuthorizationServerMetadataRoute(s.GenericAPIServer.Handler.NonGoRestfulMux, oauthMetadataEndpoint, c.ExtraConfig.MasterPublicURL)
57+
if len(c.ExtraConfig.OAuthMetadata) > 0 {
58+
initOAuthAuthorizationServerMetadataRoute(s.GenericAPIServer.Handler.NonGoRestfulMux, c.ExtraConfig)
6459
}
6560

6661
return s, nil
@@ -76,17 +71,10 @@ const (
7671
// initOAuthAuthorizationServerMetadataRoute initializes an HTTP endpoint for OAuth 2.0 Authorization Server Metadata discovery
7772
// https://tools.ietf.org/id/draft-ietf-oauth-discovery-04.html#rfc.section.2
7873
// masterPublicURL should be internally and externally routable to allow all users to discover this information
79-
func initOAuthAuthorizationServerMetadataRoute(mux *genericmux.PathRecorderMux, path, masterPublicURL string) {
80-
// Build OAuth metadata once
81-
metadata, err := json.MarshalIndent(oauthutil.GetOauthMetadata(masterPublicURL), "", " ")
82-
if err != nil {
83-
glog.Errorf("Unable to initialize OAuth authorization server metadata route: %v", err)
84-
return
85-
}
86-
87-
mux.UnlistedHandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
74+
func initOAuthAuthorizationServerMetadataRoute(mux *genericmux.PathRecorderMux, ExtraConfig *NonAPIExtraConfig) {
75+
mux.UnlistedHandleFunc(oauthMetadataEndpoint, func(w http.ResponseWriter, req *http.Request) {
8876
w.Header().Set("Content-Type", "application/json")
8977
w.WriteHeader(http.StatusOK)
90-
w.Write(metadata)
78+
w.Write(ExtraConfig.OAuthMetadata)
9179
})
9280
}

pkg/oauth/util/discovery.go

+68-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
package util
22

33
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"net/url"
8+
9+
"github.com/golang/glog"
10+
411
"github.com/RangelReale/osin"
512
"github.com/openshift/origin/pkg/authorization/authorizer/scope"
13+
configapi "github.com/openshift/origin/pkg/cmd/server/apis/config"
614
"github.com/openshift/origin/pkg/oauth/apis/oauth/validation"
715
"github.com/openshift/origin/pkg/oauthserver/osinserver"
816
)
@@ -38,7 +46,10 @@ type OauthAuthorizationServerMetadata struct {
3846
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
3947
}
4048

41-
func GetOauthMetadata(masterPublicURL string) OauthAuthorizationServerMetadata {
49+
// TODO: promote this struct as it is not effectively part of our API, since we
50+
// validate configuration using LoadOAuthMetadataFile
51+
52+
func getOauthMetadata(masterPublicURL string) OauthAuthorizationServerMetadata {
4253
config := osinserver.NewDefaultServerConfig()
4354
return OauthAuthorizationServerMetadata{
4455
Issuer: masterPublicURL,
@@ -51,3 +62,59 @@ func GetOauthMetadata(masterPublicURL string) OauthAuthorizationServerMetadata {
5162
CodeChallengeMethodsSupported: validation.CodeChallengeMethodsSupported,
5263
}
5364
}
65+
66+
func validateURL(urlString string) error {
67+
urlObj, err := url.Parse(urlString)
68+
if err != nil {
69+
return fmt.Errorf("%q is an invalid URL: %v", urlString, err)
70+
}
71+
if len(urlObj.Scheme) == 0 {
72+
return fmt.Errorf("must contain a valid scheme")
73+
}
74+
if len(urlObj.Host) == 0 {
75+
return fmt.Errorf("must contain a valid host")
76+
}
77+
return nil
78+
}
79+
80+
func LoadOAuthMetadataFile(metadataFile string) ([]byte, *OauthAuthorizationServerMetadata, error) {
81+
data, err := ioutil.ReadFile(metadataFile)
82+
if err != nil {
83+
return nil, nil, fmt.Errorf("Unable to read External OAuth Metadata file: %v", err)
84+
}
85+
86+
oauthMetadata := &OauthAuthorizationServerMetadata{}
87+
if err := json.Unmarshal(data, oauthMetadata); err != nil {
88+
return nil, nil, fmt.Errorf("Unable to decode External OAuth Metadata file: %v", err)
89+
}
90+
91+
if err := validateURL(oauthMetadata.Issuer); err != nil {
92+
return nil, nil, fmt.Errorf("Error validating External OAuth Metadata Issuer field: %v", err)
93+
}
94+
95+
if err := validateURL(oauthMetadata.AuthorizationEndpoint); err != nil {
96+
return nil, nil, fmt.Errorf("Error validating External OAuth Metadata AuthorizationEndpoint field: %v", err)
97+
}
98+
99+
if err := validateURL(oauthMetadata.TokenEndpoint); err != nil {
100+
return nil, nil, fmt.Errorf("Error validating External OAuth Metadata TokenEndpoint field: %v", err)
101+
}
102+
103+
return data, oauthMetadata, nil
104+
}
105+
106+
func PrepOauthMetadata(config configapi.MasterConfig) ([]byte, *OauthAuthorizationServerMetadata, error) {
107+
if config.OAuthConfig != nil {
108+
metadataStruct := getOauthMetadata(config.OAuthConfig.MasterPublicURL)
109+
metadata, err := json.MarshalIndent(metadataStruct, "", " ")
110+
if err != nil {
111+
glog.Errorf("Unable to initialize OAuth authorization server metadata route: %v", err)
112+
return nil, nil, err
113+
}
114+
return metadata, &metadataStruct, nil
115+
}
116+
if len(config.AuthConfig.OAuthMetadataFile) > 0 {
117+
return LoadOAuthMetadataFile(config.AuthConfig.OAuthMetadataFile)
118+
}
119+
return nil, nil, nil
120+
}

0 commit comments

Comments
 (0)