1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
auth
|
||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Docker registry
|
||||
|
||||
```bash
|
||||
openssl req -x509 -nodes -days 3650 -newkey rsa:4096 -keyout auth.key -out auth.crt -subj "/CN=omnibus-gitlab-issuer"
|
||||
|
||||
docker run --restart always --network kind --name minio -v minio:/data \
|
||||
-e MINIO_ROOT_USER=minio -e MINIO_ROOT_PASSWORD=minio123 \
|
||||
-d minio/minio server --console-address :9090 /data
|
||||
|
||||
docker run --rm --network kind -v $HOME/.mc:/root/.mc -it minio/mc alias set s3 http://minio:9000 minio minio123
|
||||
docker run --rm --network kind -v $HOME/.mc:/root/.mc -it minio/mc mb s3/registry
|
||||
docker run --rm --network kind -v $HOME/.mc:/root/.mc -it minio/mc admin user add s3 registry registry123
|
||||
docker run --rm --network kind -v $HOME/.mc:/root/.mc -it minio/mc admin policy attach s3 readwrite --user registry
|
||||
|
||||
docker run --restart always --network kind --name registry -v $PWD:/cert \
|
||||
-e REGISTRY_AUTH=token -e REGISTRY_AUTH_TOKEN_REALM=http://172.18.0.1:5001/auth \
|
||||
-e REGISTRY_AUTH_TOKEN_SERVICE=docker -e REGISTRY_AUTH_TOKEN_ISSUER=omnibus-gitlab-issuer \
|
||||
-e REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/cert/auth.crt \
|
||||
-p 0.0.0.0:5000:5000 --tmpfs /var/lib/registry -d registry:2
|
||||
|
||||
docker run --restart always --network kind --name registry --tmpfs /var/lib/registry \
|
||||
-e REGISTRY_STORAGE=s3 -e REGISTRY_STORAGE_S3_ACCESSKEY=registry -e REGISTRY_STORAGE_S3_SECRETKEY=registry123 \
|
||||
-e REGISTRY_STORAGE_S3_REGION=us-east-1 -e REGISTRY_STORAGE_S3_REGIONENDPOINT=http://minio:9000 \
|
||||
-e REGISTRY_STORAGE_S3_BUCKET=registry -e REGISTRY_STORAGE_REDIRECT_DISABLE=true \
|
||||
-p 0.0.0.0:5000:5000 -d registry:2
|
||||
```
|
||||
30
auth.crt
Normal file
30
auth.crt
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFITCCAwmgAwIBAgIUCKn3oDHzPwNRwVciKAuyyMeboYMwDQYJKoZIhvcNAQEL
|
||||
BQAwIDEeMBwGA1UEAwwVb21uaWJ1cy1naXRsYWItaXNzdWVyMB4XDTIzMDgyMjA1
|
||||
NDcwNloXDTMzMDgxOTA1NDcwNlowIDEeMBwGA1UEAwwVb21uaWJ1cy1naXRsYWIt
|
||||
aXNzdWVyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1MPpzW0v5dkt
|
||||
YXHsh8MPqZlbQhpvOhe9n7iiDjEo3yaD6pxF1Gn/bEct4bjLG1KF1/zVZonmAoel
|
||||
NqhT/lJwhcp06ML7z3e56wmk+AuSJ5KZiYeQWt9vHidI3UH7RzWHhb6hplBE7V9K
|
||||
NWheGF4Rc7KFHKrQAK7Re+N8XuY1Z2MSHSG5c0xRxEpSUesVkesmU1hT0BToC9AI
|
||||
dCOnoi33RByDBMeLf7nuKtCo5qIhBiTl1uusfDA6rGojtw085p0C32LfpzVPQGkD
|
||||
lXzOp97iXP2Z3GpUuXXvOjBuxpzvUQDcWiPN8DqjWGCFgU6WrxP3a85oAyTnqnBS
|
||||
SzTcD2VmHcA8SwhfWFgap4A872wRQ8AMVM+6y5LUoadJ/Cp03fy6WBZrqZXAsBBn
|
||||
Sc3s18LAHuYEPSxQd/dpCbLwMzuUFnYgDUfezS42pFrJcg+l2+sgbKVBBlSKDvKj
|
||||
8bz32RQ7HC28XACmi2uTMKhRDHJEoMljy4VqQ1M31h/KTHP2RfQQ0S/FW2LCIiVM
|
||||
BRnzMbUoT+HC6IzPboRwf3LUUe+RRw7UXjJrLLAuoevs/OLFjNkRuVHdUtQQaWHF
|
||||
ImpkKsiylWhtjrUjhV+fyPKXLnVH0KZS9svrqPoymmGgZmC3TmvQjX/gEf1DVrdx
|
||||
oDcREztOc+cA2mNRBuvlBwDUOWrkWF8CAwEAAaNTMFEwHQYDVR0OBBYEFE32q/qx
|
||||
88lPURQuhtu1VI3sxzK0MB8GA1UdIwQYMBaAFE32q/qx88lPURQuhtu1VI3sxzK0
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBABBLgiL0IKq2Jdt7
|
||||
H6yWG70r4aLmgOFrnxwGZjRAVS+1pJh3MiWvlqyNOjaLwHX1WxtgAQm5dsVL9H1m
|
||||
O1b1G5UrtP25192h/PzsgSP/qejSIfXAkHUPloeDzKPuJmkCNAkd+AGnVXqB1hF7
|
||||
ngrUyCMt3Ed7gny94ImQf6DVhcq8IvsddoOq+2UFPx9fGe7Xd882NzSZbi6kEmsv
|
||||
Yb28ovYi6Gsjp9o93VjTa9umtx2Kagn/+TNI5rK+bGp+lNLiTk7XkVt70pS37Fas
|
||||
eIL4IZ6pwG/G7e1MCSJkGLuXODY5ebaMj3MYn6xbJA1KsozvFv6Hmy+s8upfWYGS
|
||||
PndK/4UIcroy+zbuwPPlsoHorFiLCUW9+ThXIZ+062ahz+CX/2ETo8FpYToOhlDH
|
||||
cHKddWtx50Hl0yrJdSiq/RmKWDtspHlY5fESjbdNQauuxSrNlG+k9pq2t+jc+Mk3
|
||||
L1Yj1aFZIMie8NAmRLqC5XIhAKALqk0gPb0vpcvpZd1xs45OB/FO846z/oWnolsp
|
||||
qgeRTxUTbpLlmsYYGB4VXTepakf0F4opPnrUoxVDbT6T3uDde9N9nWN/PemCs4Mv
|
||||
8WNxs6WYi2/K5InVGrZygaLZVO4xThSWLq53cPDRZ2LPyRgRTIB8qHjc9M1X1MFY
|
||||
UNAqtEZGKvCSxfZ1UfUnTBw6HJgV
|
||||
-----END CERTIFICATE-----
|
||||
52
auth.key
Normal file
52
auth.key
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
s51eargpxc9q7tj4V1bt3njoJ86V
|
||||
-----END PRIVATE KEY-----
|
||||
8
go.mod
Normal file
8
go.mod
Normal file
@@ -0,0 +1,8 @@
|
||||
module auth
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
)
|
||||
6
go.sum
Normal file
6
go.sum
Normal file
@@ -0,0 +1,6 @@
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
90
server.go
Normal file
90
server.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
)
|
||||
|
||||
var (
|
||||
addr string
|
||||
ti TokenIssuer
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&addr, "addr", ":5001", "address")
|
||||
flag.StringVar(&ti.Issuer, "issuer", "omnibus-gitlab-issuer", "issuer name")
|
||||
flag.StringVar(&ti.Audience, "audience", "docker", "audience name")
|
||||
flag.Int64Var(&ti.Expiration, "expires", 900, "expiration")
|
||||
flag.Parse()
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
cert, err := tls.LoadX509KeyPair("auth.crt", "auth.key")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ti.SigningKey = cert.PrivateKey
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
|
||||
var resp struct {
|
||||
Token string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
ExpiresIn int64 `json:"expires_in,omitempty"`
|
||||
}
|
||||
|
||||
user, _, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
authRequest := ResolveScopeList(r.URL.Query().Get("scope"))
|
||||
if len(authRequest) == 0 {
|
||||
// Authentication-only request ("docker login"), pass through.
|
||||
resp.Token, _ = ti.CreateJWT(user, authRequest)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(&resp)
|
||||
return
|
||||
}
|
||||
|
||||
authResult, err := Authorized(user, authRequest)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Token, err = ti.CreateJWT(user, authResult); err != nil {
|
||||
log.Printf("CreateJWT %v", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
resp.ExpiresIn = ti.Expiration
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(&resp)
|
||||
})
|
||||
|
||||
h := handlers.CustomLoggingHandler(os.Stdout, mux, func(w io.Writer, p handlers.LogFormatterParams) {
|
||||
fmt.Fprintf(w, "%s %s %d %s %d %s (%s)\n", p.TimeStamp.Format("2006/01/02 15:04:05"),
|
||||
p.Request.Method, p.StatusCode, p.URL.RequestURI(), p.Size,
|
||||
p.Request.RemoteAddr, time.Since(p.TimeStamp))
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(addr, h))
|
||||
}
|
||||
|
||||
func Authorized(user string, actions []ResourceActions) ([]ResourceActions, error) {
|
||||
return actions, nil
|
||||
}
|
||||
170
token.go
Normal file
170
token.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
// Resource describes a resource by type and name.
|
||||
type Resource struct {
|
||||
Type string
|
||||
Class string
|
||||
Name string
|
||||
}
|
||||
|
||||
// ResourceActions stores allowed actions on a named and typed resource.
|
||||
type ResourceActions struct {
|
||||
Type string `json:"type"`
|
||||
Class string `json:"class,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Actions []string `json:"actions"`
|
||||
}
|
||||
|
||||
// ClaimSet describes the main section of a JSON Web Token.
|
||||
type ClaimSet struct {
|
||||
jwt.StandardClaims
|
||||
// Private claims
|
||||
Access []ResourceActions `json:"access"`
|
||||
}
|
||||
|
||||
// ResolveScopeList converts a scope list from a token request's
|
||||
// `scope` parameter into a list of standard access objects.
|
||||
func ResolveScopeList(scopeList string) []ResourceActions {
|
||||
scopeSpecs := strings.Split(scopeList, " ")
|
||||
accessSet := make(map[Resource]map[string]bool, 2*len(scopeSpecs))
|
||||
|
||||
for _, scopeSpecifier := range scopeSpecs {
|
||||
// There should be 3 parts, separated by a `:` character.
|
||||
parts := strings.SplitN(scopeSpecifier, ":", 3)
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
resourceType, resourceName, actions := parts[0], parts[1], parts[2]
|
||||
resourceType, resourceClass := splitResourceClass(resourceType)
|
||||
if resourceType == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
requestedResource := Resource{
|
||||
Type: resourceType,
|
||||
Class: resourceClass,
|
||||
Name: resourceName,
|
||||
}
|
||||
|
||||
requestedAction, has := accessSet[requestedResource]
|
||||
if !has {
|
||||
requestedAction = make(map[string]bool, 2)
|
||||
accessSet[requestedResource] = requestedAction
|
||||
}
|
||||
// Actions should be a comma-separated list of actions.
|
||||
for _, action := range strings.Split(actions, ",") {
|
||||
requestedAction[action] = true
|
||||
}
|
||||
}
|
||||
|
||||
requestedList := make([]ResourceActions, 0, len(accessSet))
|
||||
for resource, actions := range accessSet {
|
||||
ra := ResourceActions{
|
||||
Name: resource.Name,
|
||||
Class: resource.Class,
|
||||
Type: resource.Type,
|
||||
}
|
||||
for action := range actions {
|
||||
ra.Actions = append(ra.Actions, action)
|
||||
}
|
||||
sort.Strings(ra.Actions)
|
||||
requestedList = append(requestedList, ra)
|
||||
}
|
||||
return requestedList
|
||||
}
|
||||
|
||||
var typeRegexp = regexp.MustCompile(`^([a-z0-9]+)(\([a-z0-9]+\))?$`)
|
||||
|
||||
func splitResourceClass(t string) (string, string) {
|
||||
matches := typeRegexp.FindStringSubmatch(t)
|
||||
if len(matches) < 2 {
|
||||
return "", ""
|
||||
}
|
||||
if len(matches) == 2 || len(matches[2]) < 2 {
|
||||
return matches[1], ""
|
||||
}
|
||||
return matches[1], matches[2][1 : len(matches[2])-1]
|
||||
}
|
||||
|
||||
// TokenIssuer represents an issuer capable of generating JWT tokens
|
||||
type TokenIssuer struct {
|
||||
Issuer string
|
||||
Audience string
|
||||
SigningKey crypto.PrivateKey
|
||||
Expiration int64 // second
|
||||
}
|
||||
|
||||
// CreateJWT creates and signs a JSON Web Token for the given subject and
|
||||
// audience with the granted access.
|
||||
func (issuer *TokenIssuer) CreateJWT(account string, grantedAccessList []ResourceActions) (string, error) {
|
||||
randomBytes := make([]byte, 15)
|
||||
if _, err := io.ReadFull(rand.Reader, randomBytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
randomID := base64.URLEncoding.EncodeToString(randomBytes)
|
||||
|
||||
now := time.Now().Unix()
|
||||
token := ClaimSet{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Subject: account,
|
||||
Issuer: issuer.Issuer,
|
||||
Audience: issuer.Audience,
|
||||
ExpiresAt: now + issuer.Expiration,
|
||||
NotBefore: now,
|
||||
IssuedAt: now,
|
||||
Id: randomID,
|
||||
},
|
||||
Access: grantedAccessList,
|
||||
}
|
||||
|
||||
var jwtToken *jwt.Token
|
||||
switch key := issuer.SigningKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
jwtToken = jwt.NewWithClaims(jwt.SigningMethodRS256, token)
|
||||
jwtToken.Header["kid"] = keyIDEncode(key.Public())
|
||||
case *ecdsa.PrivateKey:
|
||||
jwtToken = jwt.NewWithClaims(jwt.SigningMethodES256, token)
|
||||
jwtToken.Header["kid"] = keyIDEncode(key.Public())
|
||||
default:
|
||||
return "", fmt.Errorf("unable to get PrivateKey %T", issuer.SigningKey)
|
||||
}
|
||||
|
||||
return jwtToken.SignedString(issuer.SigningKey)
|
||||
}
|
||||
|
||||
func keyIDEncode(pub crypto.PublicKey) string {
|
||||
derBytes, _ := x509.MarshalPKIXPublicKey(pub)
|
||||
sum := sha256.Sum256(derBytes)
|
||||
s := strings.TrimRight(base32.StdEncoding.EncodeToString(sum[:30]), "=")
|
||||
|
||||
var buf bytes.Buffer
|
||||
var i int
|
||||
for i = 0; i < len(s)/4-1; i++ {
|
||||
start := i * 4
|
||||
end := start + 4
|
||||
buf.WriteString(s[start:end] + ":")
|
||||
}
|
||||
buf.WriteString(s[i*4:])
|
||||
return buf.String()
|
||||
}
|
||||
Reference in New Issue
Block a user