Skip to content

Commit d02d9d6

Browse files
author
Ravi Sankar Penta
committed
OpenShift auth handler for v2 docker registry
1 parent fc590e6 commit d02d9d6

File tree

5 files changed

+331
-74
lines changed

5 files changed

+331
-74
lines changed

images/dockerregistry/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ http:
55
storage:
66
filesystem:
77
rootdirectory: /registry
8+
auth:
9+
openshift:
810
middleware:
911
repository:
1012
- name: openshift

pkg/cmd/dockerregistry/dockerregistry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
_ "github.com/docker/distribution/registry/storage/driver/s3"
1414
"github.com/docker/distribution/version"
1515
gorillahandlers "github.com/gorilla/handlers"
16+
_ "github.com/openshift/origin/pkg/dockerregistry/auth"
1617
_ "github.com/openshift/origin/pkg/dockerregistry/middleware/repository"
1718
"golang.org/x/net/context"
1819
)
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package openshift
2+
3+
import (
4+
"encoding/base64"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
"strings"
9+
10+
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11+
log "github.com/Sirupsen/logrus"
12+
ctxu "github.com/docker/distribution/context"
13+
repoauth "github.com/docker/distribution/registry/auth"
14+
authorizationapi "github.com/openshift/origin/pkg/authorization/api"
15+
"github.com/openshift/origin/pkg/dockerregistry"
16+
"golang.org/x/net/context"
17+
)
18+
19+
func init() {
20+
repoauth.Register("openshift", repoauth.InitFunc(newAccessController))
21+
}
22+
23+
type AccessController struct {
24+
UserRegistryConfig *dockerregistry.UserRegistryConfig
25+
}
26+
27+
type authChallenge struct {
28+
err error
29+
}
30+
31+
type OpenShiftAccess struct {
32+
Namespace string
33+
ImageRepo string
34+
Verb string
35+
BearerToken string
36+
}
37+
38+
var _ repoauth.AccessController = &AccessController{}
39+
var _ repoauth.Challenge = &authChallenge{}
40+
41+
// Errors used and exported by this package.
42+
var (
43+
ErrTokenRequired = errors.New("authorization header with basic token required")
44+
ErrTokenInvalid = errors.New("failed to decode basic token")
45+
ErrOpenShiftTokenRequired = errors.New("expected openshift bearer token as password for basic token to registry")
46+
ErrNamespaceRequired = errors.New("repository namespace required")
47+
ErrOpenShiftAccessDenied = errors.New("openshift access denied")
48+
)
49+
50+
func newAccessController(options map[string]interface{}) (repoauth.AccessController, error) {
51+
fmt.Println("Using OpenShift Auth handler")
52+
53+
var rc dockerregistry.UserRegistryConfig
54+
err := rc.SetRegistryConfig()
55+
if err != nil {
56+
return nil, err
57+
}
58+
return &AccessController{
59+
UserRegistryConfig: &rc,
60+
}, nil
61+
}
62+
63+
// Error returns the internal error string for this authChallenge.
64+
func (ac *authChallenge) Error() string {
65+
return ac.err.Error()
66+
}
67+
68+
// challengeParams constructs the value to be used in
69+
// the WWW-Authenticate response challenge header.
70+
// See https://tools.ietf.org/html/rfc6750#section-3
71+
func (ac *authChallenge) challengeParams() string {
72+
return fmt.Sprintf("Basic realm=openshift error=%s", ac.Error())
73+
}
74+
75+
// ServeHttp handles writing the challenge response
76+
// by setting the challenge header and status code.
77+
func (ac *authChallenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
78+
w.Header().Add("WWW-Authenticate", ac.challengeParams())
79+
w.WriteHeader(http.StatusUnauthorized)
80+
}
81+
82+
// Authorized handles checking whether the given request is authorized
83+
// for actions on resources allowed by openshift.
84+
func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...repoauth.Access) (context.Context, error) {
85+
req, err := ctxu.GetRequest(ctx)
86+
if err != nil {
87+
return nil, err
88+
}
89+
challenge := &authChallenge{}
90+
91+
authParts := strings.SplitN(req.Header.Get("Authorization"), " ", 2)
92+
if len(authParts) != 2 || strings.ToLower(authParts[0]) != "basic" {
93+
challenge.err = ErrTokenRequired
94+
return nil, challenge
95+
}
96+
basicToken := authParts[1]
97+
98+
bearerToken := ""
99+
for _, access := range accessRecords {
100+
log.Debugf("%s:%s:%s", access.Resource.Type, access.Resource.Name, access.Action)
101+
102+
if access.Resource.Type != "repository" {
103+
continue
104+
}
105+
106+
if len(bearerToken) == 0 {
107+
payload, err := base64.StdEncoding.DecodeString(basicToken)
108+
if err != nil {
109+
log.Errorf("Basic token decode failed: %s", err)
110+
challenge.err = ErrTokenInvalid
111+
return nil, challenge
112+
}
113+
authParts = strings.SplitN(string(payload), ":", 2)
114+
if len(authParts) != 2 {
115+
challenge.err = ErrOpenShiftTokenRequired
116+
return nil, challenge
117+
}
118+
bearerToken = authParts[1]
119+
}
120+
121+
repoParts := strings.SplitN(access.Resource.Name, "/", 2)
122+
if len(repoParts) != 2 {
123+
challenge.err = ErrNamespaceRequired
124+
return nil, challenge
125+
}
126+
osAccess := &OpenShiftAccess{
127+
Namespace: repoParts[0],
128+
ImageRepo: repoParts[1],
129+
BearerToken: bearerToken,
130+
}
131+
132+
switch access.Action {
133+
case "push":
134+
osAccess.Verb = "create"
135+
case "pull":
136+
osAccess.Verb = "get"
137+
default:
138+
challenge.err = fmt.Errorf("Unkown action: %s", access.Action)
139+
return nil, challenge
140+
}
141+
142+
err = VerifyOpenShiftAccess(osAccess, ac)
143+
if err != nil {
144+
challenge.err = err
145+
return nil, challenge
146+
}
147+
}
148+
return context.WithValue(ctx, "BearerToken", bearerToken), nil
149+
}
150+
151+
func VerifyOpenShiftAccess(osAccess *OpenShiftAccess, ac *AccessController) error {
152+
client, err := ac.UserRegistryConfig.GetRegistryClient(osAccess.BearerToken)
153+
if err != nil {
154+
return err
155+
}
156+
sar := authorizationapi.SubjectAccessReview{
157+
TypeMeta: kapi.TypeMeta{
158+
APIVersion: "v1beta1",
159+
Kind: "SubjectAccessReview",
160+
},
161+
Verb: osAccess.Verb,
162+
Resource: "imageRepositories",
163+
ResourceName: osAccess.ImageRepo,
164+
}
165+
response, err := client.SubjectAccessReviews(osAccess.Namespace).Create(&sar)
166+
if err != nil {
167+
log.Errorf("OpenShift client error: %s", err)
168+
return ErrOpenShiftAccessDenied
169+
}
170+
if !response.Allowed {
171+
log.Errorf("OpenShift access denied: %s", response.Reason)
172+
return ErrOpenShiftAccessDenied
173+
}
174+
return nil
175+
}

pkg/dockerregistry/helpers.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package dockerregistry
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
8+
kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
9+
osclient "github.com/openshift/origin/pkg/client"
10+
)
11+
12+
type UserRegistryConfig struct {
13+
config *kclient.Config
14+
}
15+
16+
func (urc *UserRegistryConfig) SetRegistryConfig() error {
17+
config, err := getDefaultOpenShiftClientConfig()
18+
if err != nil {
19+
return err
20+
}
21+
urc.config = config
22+
return nil
23+
}
24+
25+
func (urc *UserRegistryConfig) GetRegistryClient(bearerToken string) (*osclient.Client, error) {
26+
urc.config.BearerToken = bearerToken
27+
registryClient, err := osclient.New(urc.config)
28+
if err != nil {
29+
return nil, fmt.Errorf("Error creating OpenShift client: %s", err)
30+
}
31+
return registryClient, nil
32+
}
33+
34+
type SystemRegistryConfig struct {
35+
config *kclient.Config
36+
}
37+
38+
func (src *SystemRegistryConfig) GetRegistryClient() (*osclient.Client, error) {
39+
config, err := getDefaultOpenShiftClientConfig()
40+
if err != nil {
41+
return nil, err
42+
}
43+
var tlsClientConfig kclient.TLSClientConfig
44+
if !config.Insecure {
45+
certData := os.Getenv("OPENSHIFT_CERT_DATA")
46+
if len(certData) == 0 {
47+
return nil, errors.New("OPENSHIFT_CERT_DATA is required")
48+
}
49+
certKeyData := os.Getenv("OPENSHIFT_KEY_DATA")
50+
if len(certKeyData) == 0 {
51+
return nil, errors.New("OPENSHIFT_KEY_DATA is required")
52+
}
53+
tlsClientConfig = kclient.TLSClientConfig{
54+
CAData: config.TLSClientConfig.CAData,
55+
CertData: []byte(certData),
56+
KeyData: []byte(certKeyData),
57+
}
58+
}
59+
config.TLSClientConfig = tlsClientConfig
60+
registryClient, err := osclient.New(config)
61+
if err != nil {
62+
return nil, fmt.Errorf("Error creating OpenShift client: %s", err)
63+
}
64+
return registryClient, nil
65+
}
66+
67+
func getDefaultOpenShiftClientConfig() (*kclient.Config, error) {
68+
openshiftAddr := os.Getenv("OPENSHIFT_MASTER")
69+
if len(openshiftAddr) == 0 {
70+
return nil, errors.New("OPENSHIFT_MASTER is required")
71+
}
72+
73+
insecure := os.Getenv("OPENSHIFT_INSECURE") == "true"
74+
var tlsClientConfig kclient.TLSClientConfig
75+
if !insecure {
76+
caData := os.Getenv("OPENSHIFT_CA_DATA")
77+
if len(caData) == 0 {
78+
return nil, errors.New("OPENSHIFT_CA_DATA is required")
79+
}
80+
tlsClientConfig = kclient.TLSClientConfig{
81+
CAData: []byte(caData),
82+
}
83+
}
84+
85+
return &kclient.Config{
86+
Host: openshiftAddr,
87+
TLSClientConfig: tlsClientConfig,
88+
Insecure: insecure,
89+
}, nil
90+
}

0 commit comments

Comments
 (0)