Skip to content

Commit bde7f6f

Browse files
author
Erez Freiberger
committed
image-server: adding authentication
1 parent 760d12b commit bde7f6f

4 files changed

Lines changed: 115 additions & 14 deletions

File tree

pkg/cmd/types.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ package cmd
22

33
import (
44
"fmt"
5+
server "github.com/simon3z/image-inspector/pkg/imageserver"
6+
)
7+
8+
var (
9+
serverAuthOptions = []string{
10+
string(server.AllowAll),
11+
string(server.KubernetesToken),
12+
}
513
)
614

715
// ImageInspectorOptions is the main inspector implementation and holds the configuration
@@ -24,19 +32,22 @@ type ImageInspectorOptions struct {
2432
// PasswordFile is the location of the file containing the password for authentication to the
2533
// docker registry.
2634
PasswordFile string
35+
// ServerAuthType is the type of authentication used to access the server
36+
ServerAuthType string
2737
}
2838

2939
// NewDefaultImageInspectorOptions provides a new ImageInspectorOptions with default values.
3040
func NewDefaultImageInspectorOptions() *ImageInspectorOptions {
3141
return &ImageInspectorOptions{
32-
URI: "unix:///var/run/docker.sock",
33-
Image: "",
34-
DstPath: "",
35-
Serve: "",
36-
Chroot: false,
37-
DockerCfg: "",
38-
Username: "",
39-
PasswordFile: "",
42+
URI: "unix:///var/run/docker.sock",
43+
Image: "",
44+
DstPath: "",
45+
Serve: "",
46+
Chroot: false,
47+
DockerCfg: "",
48+
Username: "",
49+
PasswordFile: "",
50+
ServerAuthType: "None",
4051
}
4152
}
4253

@@ -57,5 +68,17 @@ func (i *ImageInspectorOptions) Validate() error {
5768
if len(i.Serve) == 0 && i.Chroot {
5869
return fmt.Errorf("Change root can be used only when serving the image through webdav")
5970
}
71+
if !stringInSlice(i.ServerAuthType, serverAuthOptions) {
72+
return fmt.Errorf("server-auth-type can only be one of %v", serverAuthOptions)
73+
}
6074
return nil
6175
}
76+
77+
func stringInSlice(str string, list []string) bool {
78+
for _, t := range list {
79+
if t == str {
80+
return true
81+
}
82+
}
83+
return false
84+
}

pkg/imageserver/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import (
44
docker "github.com/fsouza/go-dockerclient"
55
)
66

7+
type AuthenticationType string
8+
9+
const (
10+
AllowAll AuthenticationType = "None"
11+
KubernetesToken AuthenticationType = "KubenetesToken"
12+
)
13+
714
// ImageServer abstracts the serving of image information.
815
type ImageServer interface {
916
// ServeImage Serves the image
@@ -34,4 +41,6 @@ type ImageServerOptions struct {
3441
// NOTE: if the image server supports a chroot the server implementation will perform
3542
// the chroot based on this URL.
3643
ImageServeURL string
44+
// AuthType is the type of authentication used to access the server
45+
AuthType AuthenticationType
3746
}

pkg/imageserver/webdav.go

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import (
1010
"golang.org/x/net/webdav"
1111

1212
docker "github.com/fsouza/go-dockerclient"
13+
14+
kauthapi "k8s.io/kubernetes/pkg/apis/authorization"
15+
krestclient "k8s.io/kubernetes/pkg/client/restclient"
16+
kclient "k8s.io/kubernetes/pkg/client/unversioned"
1317
)
1418

1519
const (
@@ -55,29 +59,93 @@ func (s *webdavImageServer) ServeImage(imageMetadata *docker.Image) error {
5559
w.Write([]byte("ok\n"))
5660
})
5761

58-
http.HandleFunc(s.opts.APIURL, func(w http.ResponseWriter, r *http.Request) {
62+
http.HandleFunc(s.opts.APIURL, s.handlerFuncAuth(func(w http.ResponseWriter, r *http.Request) {
5963
body, err := json.MarshalIndent(s.opts.APIVersions, "", " ")
6064
if err != nil {
6165
http.Error(w, err.Error(), http.StatusInternalServerError)
6266
return
6367
}
6468
w.Write(body)
65-
})
69+
}))
6670

67-
http.HandleFunc(s.opts.MetadataURL, func(w http.ResponseWriter, r *http.Request) {
71+
http.HandleFunc(s.opts.MetadataURL, s.handlerFuncAuth(func(w http.ResponseWriter, r *http.Request) {
6872
body, err := json.MarshalIndent(imageMetadata, "", " ")
6973
if err != nil {
7074
http.Error(w, err.Error(), http.StatusInternalServerError)
7175
return
7276
}
7377
w.Write(body)
74-
})
78+
}))
7579

76-
http.Handle(s.opts.ContentURL, &webdav.Handler{
80+
http.Handle(s.opts.ContentURL, s.newAuthenticatedHandler(&webdav.Handler{
7781
Prefix: s.opts.ContentURL,
7882
FileSystem: webdav.Dir(servePath),
7983
LockSystem: webdav.NewMemLS(),
80-
})
84+
}))
8185

8286
return http.ListenAndServe(s.opts.ServePath, nil)
8387
}
88+
89+
func (s *webdavImageServer) authenticate(r *http.Request) (bool, error) {
90+
authenticator, ok := map[AuthenticationType]func(r *http.Request) (bool, error){
91+
AllowAll: allowAll,
92+
KubernetesToken: kubernetesTokenAuth,
93+
}[s.opts.AuthType]
94+
if !ok {
95+
return false, fmt.Errorf("%s is not a recognize authentication method", s.opts.AuthType)
96+
}
97+
return authenticator(r)
98+
}
99+
100+
func allowAll(r *http.Request) (bool, error) {
101+
return true, nil
102+
}
103+
104+
func kubernetesTokenAuth(r *http.Request) (bool, error) {
105+
conf, err := krestclient.InClusterConfig()
106+
if err != nil {
107+
return false, err
108+
}
109+
conf.BearerToken = r.Header.Get("Authorization")
110+
kc, err := kclient.New(conf)
111+
if err != nil {
112+
return false, err
113+
}
114+
result := &kauthapi.SubjectAccessReview{}
115+
sar := &kauthapi.SubjectAccessReview{}
116+
sar.Kind = "SubjectAccessReview"
117+
sar.APIVersion = "v1"
118+
sar.Spec.ResourceAttributes.Verb = "GET"
119+
sar.Spec.ResourceAttributes.Resource = "images"
120+
err = kc.Get().Resource("subjectAccessReview").Body(sar).Do().Into(result)
121+
if err != nil {
122+
return false, err
123+
}
124+
return result.Status.Allowed, nil
125+
}
126+
127+
func (s *webdavImageServer) handlerFuncAuth(f http.HandlerFunc) http.HandlerFunc {
128+
return func(w http.ResponseWriter, r *http.Request) {
129+
if allowed, err := s.authenticate(r); allowed && err == nil {
130+
f(w, r)
131+
} else {
132+
if err != nil {
133+
http.Error(w, err.Error(), http.StatusInternalServerError)
134+
} else {
135+
http.Error(w, "Unauthorazied Access!", http.StatusForbidden)
136+
}
137+
}
138+
}
139+
}
140+
141+
type authenticatedHandler struct {
142+
serveHttp func(http.ResponseWriter, *http.Request)
143+
}
144+
145+
func (ah *authenticatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
146+
ah.serveHttp(w, r)
147+
}
148+
149+
func (s *webdavImageServer) newAuthenticatedHandler(h http.Handler) http.Handler {
150+
return &authenticatedHandler{serveHttp: s.handlerFuncAuth(h.ServeHTTP)}
151+
}

pkg/inspector/image-inspector.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func NewDefaultImageInspector(opts iicmd.ImageInspectorOptions) ImageInspector {
6060
MetadataURL: METADATA_URL_PATH,
6161
ContentURL: CONTENT_URL_PREFIX,
6262
ImageServeURL: opts.DstPath,
63+
AuthType: opts.ServerAuthType,
6364
}
6465
inspector.imageServer = apiserver.NewWebdavImageServer(imageServerOpts, opts.Chroot)
6566
}

0 commit comments

Comments
 (0)