Skip to content

Commit 1ec25ef

Browse files
author
OpenShift Bot
authored
Merge pull request #10845 from enj/enj/f/oauth_pod_flow
Merged by openshift-bot
2 parents 9ef2b7f + fda7222 commit 1ec25ef

File tree

8 files changed

+181
-17
lines changed

8 files changed

+181
-17
lines changed

api/swagger-spec/openshift-openapi-spec.json

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@
1010
"version": "latest"
1111
},
1212
"paths": {
13+
"/.well-known/oauth-authorization-server/": {
14+
"get": {
15+
"description": "get the server's OAuth 2.0 Authorization Server Metadata",
16+
"produces": [
17+
"application/json"
18+
],
19+
"schemes": [
20+
"https"
21+
],
22+
"operationId": "getOAuthAuthorizationServerMetadata",
23+
"responses": {
24+
"default": {
25+
"description": "Default Response."
26+
}
27+
}
28+
}
29+
},
1330
"/api/": {
1431
"get": {
1532
"description": "get available API versions",
@@ -44079,9 +44096,6 @@
4407944096
"/version/openshift/": {
4408044097
"get": {
4408144098
"description": "get the code version",
44082-
"consumes": [
44083-
"application/json"
44084-
],
4408544099
"produces": [
4408644100
"application/json"
4408744101
],

pkg/authorization/api/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ var DiscoveryRule = PolicyRule{
5050
"/apis", "/apis/*",
5151
"/oapi", "/oapi/*",
5252
"/osapi", "/osapi/", // these cannot be removed until we can drop support for pre 3.1 clients
53+
"/.well-known", "/.well-known/*",
5354
),
5455
}
5556

pkg/cmd/server/api/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ type OAuthConfig struct {
635635
// MasterURL is used for making server-to-server calls to exchange authorization codes for access tokens
636636
MasterURL string
637637

638-
// MasterPublicURL is used for building valid client redirect URLs for external access
638+
// MasterPublicURL is used for building valid client redirect URLs for internal and external access
639639
MasterPublicURL string
640640

641641
// AssetPublicURL is used for building valid client redirect URLs for external access

pkg/cmd/server/origin/master.go

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import (
6969
"github.com/openshift/origin/pkg/image/registry/imagestreammapping"
7070
"github.com/openshift/origin/pkg/image/registry/imagestreamtag"
7171
oauthapi "github.com/openshift/origin/pkg/oauth/api"
72+
"github.com/openshift/origin/pkg/oauth/discovery"
7273
accesstokenetcd "github.com/openshift/origin/pkg/oauth/registry/oauthaccesstoken/etcd"
7374
authorizetokenetcd "github.com/openshift/origin/pkg/oauth/registry/oauthauthorizetoken/etcd"
7475
clientregistry "github.com/openshift/origin/pkg/oauth/registry/oauthclient"
@@ -134,6 +135,10 @@ const (
134135
OpenShiftAPIV1 = "v1"
135136
OpenShiftAPIPrefixV1 = OpenShiftAPIPrefix + "/" + OpenShiftAPIV1
136137
swaggerAPIPrefix = "/swaggerapi/"
138+
// Discovery endpoint for OAuth 2.0 Authorization Server Metadata
139+
// See IETF Draft:
140+
// https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2
141+
oauthMetadataEndpoint = "/.well-known/oauth-authorization-server"
137142
)
138143

139144
var (
@@ -446,35 +451,68 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) ([]stri
446451
initReadinessCheckRoute(root, "/healthz/ready", c.ProjectAuthorizationCache.ReadyForAccess)
447452
initVersionRoute(container, "/version/openshift")
448453

454+
// Set up OAuth metadata only if we are configured to use OAuth
455+
if c.Options.OAuthConfig != nil {
456+
initOAuthAuthorizationServerMetadataRoute(container, oauthMetadataEndpoint, c.Options.OAuthConfig.MasterPublicURL)
457+
}
458+
449459
return messages, nil
450460
}
451461

452-
// initReadinessCheckRoute initializes an HTTP endpoint for readiness checking
462+
// initVersionRoute initializes an HTTP endpoint for the server's version information.
453463
func initVersionRoute(container *restful.Container, path string) {
464+
// Build version info once
465+
versionInfo, err := json.MarshalIndent(version.Get(), "", " ")
466+
if err != nil {
467+
glog.Errorf("Unable to initialize version route: %v", err)
468+
return
469+
}
470+
454471
// Set up a service to return the git code version.
455472
versionWS := new(restful.WebService)
456473
versionWS.Path(path)
457474
versionWS.Doc("git code version from which this is built")
458475
versionWS.Route(
459-
versionWS.GET("/").To(handleVersion).
476+
versionWS.GET("/").To(func(_ *restful.Request, resp *restful.Response) {
477+
writeJSON(resp, versionInfo)
478+
}).
460479
Doc("get the code version").
461480
Operation("getCodeVersion").
462-
Produces(restful.MIME_JSON).
463-
Consumes(restful.MIME_JSON))
481+
Produces(restful.MIME_JSON))
464482

465483
container.Add(versionWS)
466484
}
467485

468-
// handleVersion writes the server's version information.
469-
func handleVersion(req *restful.Request, resp *restful.Response) {
470-
output, err := json.MarshalIndent(version.Get(), "", " ")
486+
func writeJSON(resp *restful.Response, json []byte) {
487+
resp.ResponseWriter.Header().Set("Content-Type", "application/json")
488+
resp.ResponseWriter.WriteHeader(http.StatusOK)
489+
resp.ResponseWriter.Write(json)
490+
}
491+
492+
// initOAuthAuthorizationServerMetadataRoute initializes an HTTP endpoint for OAuth 2.0 Authorization Server Metadata discovery
493+
// https://tools.ietf.org/id/draft-ietf-oauth-discovery-04.html#rfc.section.2
494+
// masterPublicURL should be internally and externally routable to allow all users to discover this information
495+
func initOAuthAuthorizationServerMetadataRoute(container *restful.Container, path, masterPublicURL string) {
496+
// Build OAuth metadata once
497+
metadata, err := json.MarshalIndent(discovery.Get(masterPublicURL, OpenShiftOAuthAuthorizeURL(masterPublicURL), OpenShiftOAuthTokenURL(masterPublicURL)), "", " ")
471498
if err != nil {
472-
http.Error(resp.ResponseWriter, err.Error(), http.StatusInternalServerError)
499+
glog.Errorf("Unable to initialize OAuth authorization server metadata route: %v", err)
473500
return
474501
}
475-
resp.ResponseWriter.Header().Set("Content-Type", "application/json")
476-
resp.ResponseWriter.WriteHeader(http.StatusOK)
477-
resp.ResponseWriter.Write(output)
502+
503+
// Set up a service to return the OAuth metadata.
504+
oauthWS := new(restful.WebService)
505+
oauthWS.Path(path)
506+
oauthWS.Doc("OAuth 2.0 Authorization Server Metadata")
507+
oauthWS.Route(
508+
oauthWS.GET("/").To(func(_ *restful.Request, resp *restful.Response) {
509+
writeJSON(resp, metadata)
510+
}).
511+
Doc("get the server's OAuth 2.0 Authorization Server Metadata").
512+
Operation("getOAuthAuthorizationServerMetadata").
513+
Produces(restful.MIME_JSON))
514+
515+
container.Add(oauthWS)
478516
}
479517

480518
func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {

pkg/oauth/api/validation/validation.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ import (
1818

1919
const MinTokenLength = 32
2020

21+
// PKCE [RFC7636] code challenge methods supported
22+
// https://tools.ietf.org/html/rfc7636#section-4.3
23+
const (
24+
codeChallengeMethodPlain = "plain"
25+
codeChallengeMethodSHA256 = "S256"
26+
)
27+
28+
var CodeChallengeMethodsSupported = []string{codeChallengeMethodPlain, codeChallengeMethodSHA256}
29+
2130
func ValidateTokenName(name string, prefix bool) []string {
2231
if reasons := oapi.MinimalNameRequirements(name, prefix); len(reasons) != 0 {
2332
return reasons
@@ -101,10 +110,10 @@ func ValidateAuthorizeToken(authorizeToken *api.OAuthAuthorizeToken) field.Error
101110
switch authorizeToken.CodeChallengeMethod {
102111
case "":
103112
allErrs = append(allErrs, field.Required(field.NewPath("codeChallengeMethod"), "required if codeChallenge is specified"))
104-
case "plain", "S256":
113+
case codeChallengeMethodPlain, codeChallengeMethodSHA256:
105114
// no-op, good
106115
default:
107-
allErrs = append(allErrs, field.NotSupported(field.NewPath("codeChallengeMethod"), authorizeToken.CodeChallengeMethod, []string{"plain", "S256"}))
116+
allErrs = append(allErrs, field.NotSupported(field.NewPath("codeChallengeMethod"), authorizeToken.CodeChallengeMethod, CodeChallengeMethodsSupported))
108117
}
109118
}
110119

pkg/oauth/discovery/discovery.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package discovery
2+
3+
import (
4+
"github.com/RangelReale/osin"
5+
"github.com/openshift/origin/pkg/authorization/authorizer/scope"
6+
"github.com/openshift/origin/pkg/oauth/api/validation"
7+
"github.com/openshift/origin/pkg/oauth/server/osinserver"
8+
)
9+
10+
// OauthAuthorizationServerMetadata holds OAuth 2.0 Authorization Server Metadata used for discovery
11+
// https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2
12+
type OauthAuthorizationServerMetadata struct {
13+
// The authorization server's issuer identifier, which is a URL that uses the https scheme and has no query or fragment components.
14+
// This is the location where .well-known RFC 5785 [RFC5785] resources containing information about the authorization server are published.
15+
Issuer string `json:"issuer"`
16+
17+
// URL of the authorization server's authorization endpoint [RFC6749].
18+
AuthorizationEndpoint string `json:"authorization_endpoint"`
19+
20+
// URL of the authorization server's token endpoint [RFC6749].
21+
TokenEndpoint string `json:"token_endpoint"`
22+
23+
// JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this authorization server supports.
24+
// Servers MAY choose not to advertise some supported scope values even when this parameter is used.
25+
ScopesSupported []string `json:"scopes_supported"`
26+
27+
// JSON array containing a list of the OAuth 2.0 response_type values that this authorization server supports.
28+
// The array values used are the same as those used with the response_types parameter defined by "OAuth 2.0 Dynamic Client Registration Protocol" [RFC7591].
29+
ResponseTypesSupported osin.AllowedAuthorizeType `json:"response_types_supported"`
30+
31+
// JSON array containing a list of the OAuth 2.0 grant type values that this authorization server supports.
32+
// The array values used are the same as those used with the grant_types parameter defined by "OAuth 2.0 Dynamic Client Registration Protocol" [RFC7591].
33+
GrantTypesSupported osin.AllowedAccessType `json:"grant_types_supported"`
34+
35+
// JSON array containing a list of PKCE [RFC7636] code challenge methods supported by this authorization server.
36+
// Code challenge method values are used in the "code_challenge_method" parameter defined in Section 4.3 of [RFC7636].
37+
// The valid code challenge method values are those registered in the IANA "PKCE Code Challenge Methods" registry [IANA.OAuth.Parameters].
38+
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
39+
}
40+
41+
func Get(masterPublicURL, authorizeURL, tokenURL string) OauthAuthorizationServerMetadata {
42+
config := osinserver.NewDefaultServerConfig()
43+
return OauthAuthorizationServerMetadata{
44+
Issuer: masterPublicURL,
45+
AuthorizationEndpoint: authorizeURL,
46+
TokenEndpoint: tokenURL,
47+
ScopesSupported: []string{ // Note: this list is incomplete, which is allowed per the draft spec
48+
scope.UserFull,
49+
scope.UserInfo,
50+
scope.UserAccessCheck,
51+
scope.UserListScopedProjects,
52+
scope.UserListAllProjects,
53+
},
54+
ResponseTypesSupported: config.AllowedAuthorizeTypes,
55+
GrantTypesSupported: osin.AllowedAccessType{osin.AUTHORIZATION_CODE, osin.AccessRequestType("implicit")}, // TODO use config.AllowedAccessTypes once our implementation handles other grant types
56+
CodeChallengeMethodsSupported: validation.CodeChallengeMethodsSupported,
57+
}
58+
}

pkg/oauth/discovery/discovery_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package discovery
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/RangelReale/osin"
8+
)
9+
10+
func TestGet(t *testing.T) {
11+
actual := Get("https://localhost:8443", "https://localhost:8443/oauth/authorize", "https://localhost:8443/oauth/token")
12+
expected := OauthAuthorizationServerMetadata{
13+
Issuer: "https://localhost:8443",
14+
AuthorizationEndpoint: "https://localhost:8443/oauth/authorize",
15+
TokenEndpoint: "https://localhost:8443/oauth/token",
16+
ScopesSupported: []string{
17+
"user:full",
18+
"user:info",
19+
"user:check-access",
20+
"user:list-scoped-projects",
21+
"user:list-projects",
22+
},
23+
ResponseTypesSupported: osin.AllowedAuthorizeType{
24+
"code",
25+
"token",
26+
},
27+
GrantTypesSupported: osin.AllowedAccessType{
28+
"authorization_code",
29+
"implicit",
30+
},
31+
CodeChallengeMethodsSupported: []string{
32+
"plain",
33+
"S256",
34+
},
35+
}
36+
37+
if !reflect.DeepEqual(actual, expected) {
38+
t.Errorf("Expected %#v, got %#v", expected, actual)
39+
}
40+
}

test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,8 @@ items:
14971497
- apiGroups: null
14981498
attributeRestrictions: null
14991499
nonResourceURLs:
1500+
- /.well-known
1501+
- /.well-known/*
15001502
- /api
15011503
- /api/*
15021504
- /apis
@@ -2114,6 +2116,8 @@ items:
21142116
- apiGroups: null
21152117
attributeRestrictions: null
21162118
nonResourceURLs:
2119+
- /.well-known
2120+
- /.well-known/*
21172121
- /api
21182122
- /api/*
21192123
- /apis

0 commit comments

Comments
 (0)