From dd8ea4b8429b5e31bf102f77fdda866186e2cb6f Mon Sep 17 00:00:00 2001
From: rojer
Date: Tue, 2 Jan 2018 08:35:55 +0300
Subject: [PATCH 001/128] Add missing var
---
auth_server/server/server.go | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/auth_server/server/server.go b/auth_server/server/server.go
index 75dffba9..345facbb 100644
--- a/auth_server/server/server.go
+++ b/auth_server/server/server.go
@@ -219,7 +219,7 @@ func (as *AuthServer) Authenticate(ar *authRequest) (bool, authn.Labels, error)
if err == authn.NoMatch {
continue
} else if err == authn.WrongPass {
- glog.Warningf("Failed authentication with %s: %s", err)
+ glog.Warningf("Failed authentication with %s: %s", err, ar.Account)
return false, nil, nil
}
err = fmt.Errorf("authn #%d returned error: %s", i+1, err)
@@ -334,13 +334,13 @@ func (as *AuthServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
glog.V(3).Infof("Request: %+v", req)
path_prefix := as.config.Server.PathPrefix
switch {
- case req.URL.Path == path_prefix + "/":
+ case req.URL.Path == path_prefix+"/":
as.doIndex(rw, req)
- case req.URL.Path == path_prefix + "/auth":
+ case req.URL.Path == path_prefix+"/auth":
as.doAuth(rw, req)
- case req.URL.Path == path_prefix + "/google_auth" && as.ga != nil:
+ case req.URL.Path == path_prefix+"/google_auth" && as.ga != nil:
as.ga.DoGoogleAuth(rw, req)
- case req.URL.Path == path_prefix + "/github_auth" && as.gha != nil:
+ case req.URL.Path == path_prefix+"/github_auth" && as.gha != nil:
as.gha.DoGitHubAuth(rw, req)
default:
http.Error(rw, "Not found", http.StatusNotFound)
From 180f5707c9269bf223cff8cb15921f1f4b061e0d Mon Sep 17 00:00:00 2001
From: Carson A
Date: Fri, 2 Feb 2018 14:53:04 -0700
Subject: [PATCH 002/128] Update example mongo rule order (#214)
Fixes #213
---
docs/Backend_MongoDB.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/Backend_MongoDB.md b/docs/Backend_MongoDB.md
index 5a322ead..a5d2b27a 100644
--- a/docs/Backend_MongoDB.md
+++ b/docs/Backend_MongoDB.md
@@ -62,10 +62,10 @@ guarantee by default, i.e. [Natural Sorting](https://docs.mongodb.org/manual/ref
{"seq": 11, "match" : {"labels": {"group": "admin"}}, "actions" : ["*"], "comment" : "Admin group members have full access to everything"}
{"seq": 20, "match" : {"account" : "test", "name" : "test-*"}, "actions" : ["*"], "comment" : "User \"test\" has full access to test-* images but nothing else. (1)"}
{"seq": 30, "match" : {"account" : "test"}, "actions" : [], "comment" : "User \"test\" has full access to test-* images but nothing else. (2)"}
-{"seq": 40, "match" : {"account" : "/.+/"}, "actions" : ["pull"], "comment" : "All logged in users can pull all images."}
-{"seq": 50, "match" : {"account" : "/.+/", "name" : "${account}/*"}, "actions" : ["*"], "comment" : "All logged in users can push all images that are in a namespace beginning with their name"}
-{"seq": 60, "match" : {"name" : "${labels:group}-shared/*"}, "actions" : ["push", "pull"], "comment" : "Users can pull and push to the shared namespace of any group they are in"}
-{"seq": 70, "match" : {"name" : "${labels:project}/*"}, "actions" : ["push", "pull"], "comment" : "Users can pull and push to to namespaces matching projects they are assigned to"}
+{"seq": 40, "match" : {"account" : "/.+/", "name" : "${account}/*"}, "actions" : ["*"], "comment" : "All logged in users can push all images that are in a namespace beginning with their name"}
+{"seq": 50, "match" : {"name" : "${labels:group}-shared/*"}, "actions" : ["push", "pull"], "comment" : "Users can pull and push to the shared namespace of any group they are in"}
+{"seq": 60, "match" : {"name" : "${labels:project}/*"}, "actions" : ["push", "pull"], "comment" : "Users can pull and push to to namespaces matching projects they are assigned to"}
+{"seq": 70, "match" : {"account" : "/.+/"}, "actions" : ["pull"], "comment" : "All logged in users can pull all images."}
{"seq": 80, "match" : {"account" : "", "name" : "hello-world"}, "actions" : ["pull"], "comment" : "Anonymous users can pull \"hello-world\"."}
```
From 7c0b9aa5c6557f8874fccf876323039fd434cf1a Mon Sep 17 00:00:00 2001
From: rojer
Date: Fri, 2 Feb 2018 21:58:32 +0000
Subject: [PATCH 003/128] Pass cache dir to the LetsEncrypt manager
Fixes #204
---
auth_server/main.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/auth_server/main.go b/auth_server/main.go
index 2337e7ba..aa0056bf 100644
--- a/auth_server/main.go
+++ b/auth_server/main.go
@@ -79,6 +79,7 @@ func ServeOnce(c *server.Config, cf string, hd *httpdown.HTTP) (*server.AuthServ
} else if c.Server.LetsEncrypt.Email != "" {
m := &autocert.Manager{
Email: c.Server.LetsEncrypt.Email,
+ Cache: autocert.DirCache(c.Server.LetsEncrypt.CacheDir),
Prompt: autocert.AcceptTOS,
}
if c.Server.LetsEncrypt.Host != "" {
From 509a03a9622f460ded806a2c7b7b27717b3cb1f5 Mon Sep 17 00:00:00 2001
From: Shuanglei Tao
Date: Sat, 3 Feb 2018 06:08:01 +0800
Subject: [PATCH 004/128] Add a non-tls example config (#209)
---
examples/non_tls.yml | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
create mode 100644 examples/non_tls.yml
diff --git a/examples/non_tls.yml b/examples/non_tls.yml
new file mode 100644
index 00000000..1f28a90c
--- /dev/null
+++ b/examples/non_tls.yml
@@ -0,0 +1,33 @@
+# A non-tls example. See reference.yml for explanation of all options.
+#
+# auth:
+# token:
+# realm: "http://127.0.0.1:5001/auth"
+# service: "Docker registry"
+# issuer: "Acme auth server"
+# rootcertbundle: "/path/to/server.pem"
+
+server:
+ addr: ":5001"
+
+token:
+ issuer: "Acme auth server" # Must match issuer in the Registry config.
+ expiration: 900
+ certificate: "/path/to/server.pem"
+ key: "/path/to/server.key"
+
+users:
+ # Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
+ "admin":
+ password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin
+ "test":
+ password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123
+
+acl:
+ - match: {account: "admin"}
+ actions: ["*"]
+ comment: "Admin has full access to everything."
+ - match: {account: "user"}
+ actions: ["pull"]
+ comment: "User \"user\" can pull stuff."
+ # Access is denied by default.
From 2ee85ad8040bab72a929958b4c3c8037dbcd31ae Mon Sep 17 00:00:00 2001
From: Kevin
Date: Mon, 27 Feb 2017 19:09:52 +1300
Subject: [PATCH 005/128] Initial proof of concept mapping memberOf CN to the
label groups #63
(cherry picked from commit 4a33badac6b74617dfe3797a716a6907cf018b27)
---
auth_server/authn/ldap_auth.go | 73 ++++++++++++++++++++++++++++------
1 file changed, 60 insertions(+), 13 deletions(-)
diff --git a/auth_server/authn/ldap_auth.go b/auth_server/authn/ldap_auth.go
index a3425ed6..57690575 100644
--- a/auth_server/authn/ldap_auth.go
+++ b/auth_server/authn/ldap_auth.go
@@ -17,7 +17,6 @@
package authn
import (
- "bytes"
"crypto/tls"
"crypto/x509"
"fmt"
@@ -73,10 +72,20 @@ func (la *LDAPAuth) Authenticate(account string, password PasswordString) (bool,
account = la.escapeAccountInput(account)
filter := la.getFilter(account)
- accountEntryDN, uSearchErr := la.ldapSearch(l, &la.config.Base, &filter, &[]string{})
+
+ // dnAndGroupAttr := []string{"DN"} // example of no groups mapping attribute
+ groupAttribute := "memberOf"
+ dnAndGroupAttr := []string{"DN", groupAttribute}
+
+ entryAttrMap, uSearchErr := la.ldapSearch(l, &la.config.Base, &filter, &dnAndGroupAttr)
if uSearchErr != nil {
return false, nil, uSearchErr
}
+ if len(entryAttrMap) < 1 || entryAttrMap["DN"] == nil || len(entryAttrMap["DN"]) != 1 {
+ return false, nil, NoMatch // User does not exist
+ }
+
+ accountEntryDN := entryAttrMap["DN"][0]
if accountEntryDN == "" {
return false, nil, NoMatch // User does not exist
}
@@ -95,6 +104,20 @@ func (la *LDAPAuth) Authenticate(account string, password PasswordString) (bool,
return false, nil, bindErr
}
+ // Extract group names from the attribute values
+ if entryAttrMap[groupAttribute] != nil {
+ rawGroups := entryAttrMap[groupAttribute]
+ labels := make(map[string][]string)
+ var groups []string
+ for _, value := range rawGroups {
+ cn := la.getCNFromDN(value)
+ groups = append(groups, cn)
+ }
+ labels["groups"] = groups
+
+ return true, labels, nil
+ }
+
return true, nil, nil
}
@@ -185,9 +208,9 @@ func (la *LDAPAuth) getFilter(account string) string {
//ldap search and return required attributes' value from searched entries
//default return entry's DN value if you leave attrs array empty
-func (la *LDAPAuth) ldapSearch(l *ldap.Conn, baseDN *string, filter *string, attrs *[]string) (string, error) {
+func (la *LDAPAuth) ldapSearch(l *ldap.Conn, baseDN *string, filter *string, attrs *[]string) (map[string][]string, error) {
if l == nil {
- return "", fmt.Errorf("No ldap connection!")
+ return nil, fmt.Errorf("No ldap connection!")
}
glog.V(2).Infof("Searching...basedDN:%s, filter:%s", *baseDN, *filter)
searchRequest := ldap.NewSearchRequest(
@@ -198,30 +221,54 @@ func (la *LDAPAuth) ldapSearch(l *ldap.Conn, baseDN *string, filter *string, att
nil)
sr, err := l.Search(searchRequest)
if err != nil {
- return "", err
+ return nil, err
}
if len(sr.Entries) == 0 {
- return "", nil // User does not exist
+ return nil, nil // User does not exist
} else if len(sr.Entries) > 1 {
- return "", fmt.Errorf("Too many entries returned.")
+ return nil, fmt.Errorf("Too many entries returned.")
}
- var buffer bytes.Buffer
+ result := make(map[string][]string)
for _, entry := range sr.Entries {
+
if len(*attrs) == 0 {
glog.V(2).Infof("Entry DN = %s", entry.DN)
- buffer.WriteString(entry.DN)
+ result["DN"] = []string{entry.DN}
} else {
for _, attr := range *attrs {
- values := strings.Join(entry.GetAttributeValues(attr), " ")
- glog.V(2).Infof("Entry %s = %s", attr, values)
- buffer.WriteString(values)
+ var values []string
+ if attr == "DN" {
+ // DN is excluded from attributes
+ values = []string{entry.DN}
+ } else {
+ values = entry.GetAttributeValues(attr)
+ }
+ valuesString := strings.Join(values, "\n")
+ glog.V(2).Infof("Entry %s = %s", attr, valuesString)
+ result[attr] = values
+ }
+ }
+ }
+
+ return result, nil
+}
+
+func (la *LDAPAuth) getCNFromDN(dn string) string {
+ parsedDN, err := ldap.ParseDN(dn)
+ if err != nil || len(parsedDN.RDNs) > 0 {
+ for _, rdn := range parsedDN.RDNs {
+ for _, rdnAttr := range rdn.Attributes {
+ if rdnAttr.Type == "CN" {
+ return rdnAttr.Value
+ }
}
}
}
- return buffer.String(), nil
+ // else try using raw DN
+ return dn
}
func (la *LDAPAuth) Stop() {
From 3f5e1b78519238ca65e6084f48cbdd56531e4c84 Mon Sep 17 00:00:00 2001
From: Kevin
Date: Tue, 28 Feb 2017 18:09:55 +1300
Subject: [PATCH 006/128] Apply attribute mapping from configuration
(cherry picked from commit ddde2fa779e746d7e74cd972a4c6795c72f17ee6)
---
auth_server/authn/ldap_auth.go | 127 +++++++++++++++++++--------------
1 file changed, 75 insertions(+), 52 deletions(-)
diff --git a/auth_server/authn/ldap_auth.go b/auth_server/authn/ldap_auth.go
index 57690575..99c91466 100644
--- a/auth_server/authn/ldap_auth.go
+++ b/auth_server/authn/ldap_auth.go
@@ -27,17 +27,23 @@ import (
"github.com/cesanta/glog"
)
+type LabelMap struct {
+ Attribute string `yaml:"attribute,omitempty"`
+ ParseCN bool `yaml:"parse_cn,omitempty"`
+}
+
type LDAPAuthConfig struct {
- Addr string `yaml:"addr,omitempty"`
- TLS string `yaml:"tls,omitempty"`
- InsecureTLSSkipVerify bool `yaml:"insecure_tls_skip_verify,omitempty"`
- CACertificate string `yaml:"ca_certificate,omitempty"`
- Base string `yaml:"base,omitempty"`
- Filter string `yaml:"filter,omitempty"`
- BindDN string `yaml:"bind_dn,omitempty"`
- BindPasswordFile string `yaml:"bind_password_file,omitempty"`
- GroupBaseDN string `yaml:"group_base_dn,omitempty"`
- GroupFilter string `yaml:"group_filter,omitempty"`
+ Addr string `yaml:"addr,omitempty"`
+ TLS string `yaml:"tls,omitempty"`
+ InsecureTLSSkipVerify bool `yaml:"insecure_tls_skip_verify,omitempty"`
+ CACertificate string `yaml:"ca_certificate,omitempty"`
+ Base string `yaml:"base,omitempty"`
+ Filter string `yaml:"filter,omitempty"`
+ BindDN string `yaml:"bind_dn,omitempty"`
+ BindPasswordFile string `yaml:"bind_password_file,omitempty"`
+ LabelMaps map[string]LabelMap `yaml:"labels,omitempty"`
+ GroupBaseDN string `yaml:"group_base_dn,omitempty"`
+ GroupFilter string `yaml:"group_filter,omitempty"`
}
type LDAPAuth struct {
@@ -73,22 +79,19 @@ func (la *LDAPAuth) Authenticate(account string, password PasswordString) (bool,
filter := la.getFilter(account)
- // dnAndGroupAttr := []string{"DN"} // example of no groups mapping attribute
- groupAttribute := "memberOf"
- dnAndGroupAttr := []string{"DN", groupAttribute}
+ labelAttributes, labelsConfigErr := la.getLabelAttributes()
+ if labelsConfigErr != nil {
+ return false, nil, labelsConfigErr
+ }
- entryAttrMap, uSearchErr := la.ldapSearch(l, &la.config.Base, &filter, &dnAndGroupAttr)
+ accountEntryDN, entryAttrMap, uSearchErr := la.ldapSearch(l, &la.config.Base, &filter, &labelAttributes)
if uSearchErr != nil {
return false, nil, uSearchErr
}
- if len(entryAttrMap) < 1 || entryAttrMap["DN"] == nil || len(entryAttrMap["DN"]) != 1 {
- return false, nil, NoMatch // User does not exist
- }
-
- accountEntryDN := entryAttrMap["DN"][0]
if accountEntryDN == "" {
return false, nil, NoMatch // User does not exist
}
+
// Bind as the user to verify their password
if len(accountEntryDN) > 0 {
err := l.Bind(accountEntryDN, string(password))
@@ -104,21 +107,13 @@ func (la *LDAPAuth) Authenticate(account string, password PasswordString) (bool,
return false, nil, bindErr
}
- // Extract group names from the attribute values
- if entryAttrMap[groupAttribute] != nil {
- rawGroups := entryAttrMap[groupAttribute]
- labels := make(map[string][]string)
- var groups []string
- for _, value := range rawGroups {
- cn := la.getCNFromDN(value)
- groups = append(groups, cn)
- }
- labels["groups"] = groups
-
- return true, labels, nil
+ // Extract labels from the attribute values
+ labels, labelsExtractErr := la.getLabelsFromMap(entryAttrMap)
+ if labelsExtractErr != nil {
+ return false, nil, labelsExtractErr
}
- return true, nil, nil
+ return true, labels, nil
}
func (la *LDAPAuth) bindReadOnlyUser(l *ldap.Conn) error {
@@ -208,9 +203,9 @@ func (la *LDAPAuth) getFilter(account string) string {
//ldap search and return required attributes' value from searched entries
//default return entry's DN value if you leave attrs array empty
-func (la *LDAPAuth) ldapSearch(l *ldap.Conn, baseDN *string, filter *string, attrs *[]string) (map[string][]string, error) {
+func (la *LDAPAuth) ldapSearch(l *ldap.Conn, baseDN *string, filter *string, attrs *[]string) (string, map[string][]string, error) {
if l == nil {
- return nil, fmt.Errorf("No ldap connection!")
+ return "", nil, fmt.Errorf("No ldap connection!")
}
glog.V(2).Infof("Searching...basedDN:%s, filter:%s", *baseDN, *filter)
searchRequest := ldap.NewSearchRequest(
@@ -221,38 +216,66 @@ func (la *LDAPAuth) ldapSearch(l *ldap.Conn, baseDN *string, filter *string, att
nil)
sr, err := l.Search(searchRequest)
if err != nil {
- return nil, err
+ return "", nil, err
}
if len(sr.Entries) == 0 {
- return nil, nil // User does not exist
+ return "", nil, nil // User does not exist
} else if len(sr.Entries) > 1 {
- return nil, fmt.Errorf("Too many entries returned.")
+ return "", nil, fmt.Errorf("Too many entries returned.")
}
- result := make(map[string][]string)
+ attributes := make(map[string][]string)
+ var entryDn string
for _, entry := range sr.Entries {
-
+ entryDn = entry.DN
if len(*attrs) == 0 {
- glog.V(2).Infof("Entry DN = %s", entry.DN)
- result["DN"] = []string{entry.DN}
+ glog.V(2).Infof("Entry DN = %s", entryDn)
} else {
for _, attr := range *attrs {
- var values []string
- if attr == "DN" {
- // DN is excluded from attributes
- values = []string{entry.DN}
- } else {
- values = entry.GetAttributeValues(attr)
- }
- valuesString := strings.Join(values, "\n")
- glog.V(2).Infof("Entry %s = %s", attr, valuesString)
- result[attr] = values
+ values := entry.GetAttributeValues(attr)
+ glog.V(2).Infof("Entry %s = %s", attr, strings.Join(values, "\n"))
+ attributes[attr] = values
}
}
}
- return result, nil
+ return entryDn, attributes, nil
+}
+
+func (la *LDAPAuth) getLabelAttributes() ([]string, error) {
+ labelAttributes := make([]string, len(la.config.LabelMaps))
+ i := 0
+ for key, mapping := range la.config.LabelMaps {
+ if mapping.Attribute == "" {
+ return nil, fmt.Errorf("Label %s is missing 'attribute' to map from", key)
+ }
+ labelAttributes[i] = mapping.Attribute
+ i++
+ }
+ return labelAttributes, nil
+}
+
+func (la *LDAPAuth) getLabelsFromMap(attrMap map[string][]string) (map[string][]string, error) {
+ labels := make(map[string][]string)
+ for key, mapping := range la.config.LabelMaps {
+ if mapping.Attribute == "" {
+ return nil, fmt.Errorf("Label %s is missing 'attribute' to map from", key)
+ }
+
+ mappingValues := attrMap[mapping.Attribute]
+ if mappingValues != nil {
+ if mapping.ParseCN {
+ // shorten attribute to its common name
+ for i, value := range mappingValues {
+ cn := la.getCNFromDN(value)
+ mappingValues[i] = cn
+ }
+ }
+ labels[key] = mappingValues
+ }
+ }
+ return labels, nil
}
func (la *LDAPAuth) getCNFromDN(dn string) string {
From 98c4191ee4eae3e3e823c91226179c740e77f3a9 Mon Sep 17 00:00:00 2001
From: Kevin
Date: Tue, 28 Feb 2017 18:27:16 +1300
Subject: [PATCH 007/128] Remove unused configuration fields, never
implemented?
(cherry picked from commit cd37001980267a99a9faa19f1927891af63acb90)
---
auth_server/authn/ldap_auth.go | 2 --
1 file changed, 2 deletions(-)
diff --git a/auth_server/authn/ldap_auth.go b/auth_server/authn/ldap_auth.go
index 99c91466..1135dad3 100644
--- a/auth_server/authn/ldap_auth.go
+++ b/auth_server/authn/ldap_auth.go
@@ -42,8 +42,6 @@ type LDAPAuthConfig struct {
BindDN string `yaml:"bind_dn,omitempty"`
BindPasswordFile string `yaml:"bind_password_file,omitempty"`
LabelMaps map[string]LabelMap `yaml:"labels,omitempty"`
- GroupBaseDN string `yaml:"group_base_dn,omitempty"`
- GroupFilter string `yaml:"group_filter,omitempty"`
}
type LDAPAuth struct {
From 1b5d134966c8bd1cba9afaeca284476e66a495e5 Mon Sep 17 00:00:00 2001
From: Kevin
Date: Fri, 1 Sep 2017 22:50:19 +1200
Subject: [PATCH 008/128] Add LDAP label map examples to the reference config
(cherry picked from commit 2fd43be4e5c2cfe177d9e1d36bcd1b29f4d6f262)
---
examples/reference.yml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/examples/reference.yml b/examples/reference.yml
index 6ab4ba29..26182fdc 100644
--- a/examples/reference.yml
+++ b/examples/reference.yml
@@ -140,6 +140,16 @@ ldap_auth:
# User query settings. ${account} is expanded from auth request
base: o=example.com
filter: (&(uid=${account})(objectClass=person))
+ # Labels can be mapped from LDAP attributes
+ labels:
+ # Add the user's title to a label called title
+ title:
+ attribute: title
+ # Add the user's memberOf values to a label called groups
+ groups:
+ attribute: memberOf
+ # Special handling to simplify the values to just the common name
+ parse_cn: true
mongo_auth:
# Essentially all options are described here: https://godoc.org/gopkg.in/mgo.v2#DialInfo
From 1bc75974e70ff7a84bdf3323889b81e44ea3dc00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Manuel=20R=C3=BCger?=
Date: Thu, 12 Apr 2018 15:00:51 +0200
Subject: [PATCH 009/128] reference.yml: Add example ACL
---
examples/reference.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/examples/reference.yml b/examples/reference.yml
index 26182fdc..4bdec24a 100644
--- a/examples/reference.yml
+++ b/examples/reference.yml
@@ -263,6 +263,12 @@ acl:
- match: {name: "${labels:project}-{labels:tier}/*"}
actions: ["push", "pull"]
comment: "Users can push to a project-tier/* that they are assigned to"
+ - match: {labels: {"title": "Developer"}}
+ actions: ["*"]
+ comment: "If you call yourself a developer you can do anything (this ACL is an example for LDAP labels as defined above)"
+ - match: {labels: {"groups": "Admin"}}
+ actions: ["push"]
+ comment: "If you are part of the admin group you can push. (this ACL is an example for LDAP labels as defined above)"
# Access is denied by default.
# (optional) Define to query ACL from a MongoDB server.
From 6c06b8bca2cb7429e9f39a9bba264c63c320d553 Mon Sep 17 00:00:00 2001
From: Karel Minarik
Date: Fri, 1 Jun 2018 03:40:16 +0200
Subject: [PATCH 010/128] Add authorization based on Github teams (#219)
* [authn] Added the fetching of Github teams into token `labels`
This patch adds the ability to:
* Fetch user's team in configured organization (the `fetchTeams` function), with suppor for pagination of resuls
* Store them as list of strings in the `TokenDBValue` struct
* Return them as `labels` in the `Authenticate` function
The motivation is to use the list of user's teams as `labels` in the ACL configuration, eg.:
- match: {labels: {"teams": "developers"}}
actions: ["*"]
comment: Developers have access to everything
The Github API used, https://developer.github.com/v3/orgs/teams/#list-user-teams, is currently marked as
experimental, but it's currently the only way how to fetch the teams of a user.
The teams are fetched _only_ when a Github organization is configured in YAML, with the assumption that
it will be used in context of a company Docker registry, which eg. allows public access for pulling,
and private access for pushing the images.
Implements: https://github.com/cesanta/docker_auth/issues/191
This patch improves the visual style of the `/github_auth` page, adding some CSS and better HTML structure.
The Github logo is added as an inline SVG string in the CSS declaration.
It also adds a new HTML page for the subsequent page, when user has been authenticated at Github,
showing a formatted `docker login` command.
It also changes the handler in `server.go` to redirect to `/github_auth` immediately, when it was
configured in the YAML file, to skip and unnecessary step.
---
auth_server/authn/bindata.go | 142 ++++++++++-
auth_server/authn/data/github_auth.tmpl | 75 +++++-
.../authn/data/github_auth_result.tmpl | 45 ++++
auth_server/authn/github_auth.go | 231 ++++++++++++++++--
auth_server/server/server.go | 18 +-
5 files changed, 481 insertions(+), 30 deletions(-)
create mode 100644 auth_server/authn/data/github_auth_result.tmpl
diff --git a/auth_server/authn/bindata.go b/auth_server/authn/bindata.go
index 5437967a..13f07a6a 100644
--- a/auth_server/authn/bindata.go
+++ b/auth_server/authn/bindata.go
@@ -1,6 +1,7 @@
// Code generated by go-bindata.
// sources:
// data/github_auth.tmpl
+// data/github_auth_result.tmpl
// data/google_auth.tmpl
// DO NOT EDIT!
@@ -45,10 +46,79 @@ func (fi bindataFileInfo) Sys() interface{} {
return nil
}
-var _dataGithub_authTmpl = []byte(`
+var _dataGithub_authTmpl = []byte(`
+
+
+
+
+ Docker Registry Authentication
+
+
+
- Login with GitHub
- Revoke access
+
`)
@@ -63,7 +133,69 @@ func dataGithub_authTmpl() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "data/github_auth.tmpl", size: 359, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
+ info := bindataFileInfo{name: "data/github_auth.tmpl", size: 2946, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _dataGithub_auth_resultTmpl = []byte(`
+
+
+
+
+ Docker Registry Authentication
+
+
+
+
+ You are successfully authenticated for the Docker Registry{{if .Organization}} with the @{{.Organization}} Github organization{{end}}.
+ Use the following username and password to login into the registry:
+
+
+ $ docker login -u {{.Username}} -p {{.Password}} YOUR_REGISTRY_FQDN
+
+
+`)
+
+func dataGithub_auth_resultTmplBytes() ([]byte, error) {
+ return _dataGithub_auth_resultTmpl, nil
+}
+
+func dataGithub_auth_resultTmpl() (*asset, error) {
+ bytes, err := dataGithub_auth_resultTmplBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "data/github_auth_result.tmpl", size: 1094, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -228,6 +360,7 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"data/github_auth.tmpl": dataGithub_authTmpl,
+ "data/github_auth_result.tmpl": dataGithub_auth_resultTmpl,
"data/google_auth.tmpl": dataGoogle_authTmpl,
}
@@ -273,6 +406,7 @@ type bintree struct {
var _bintree = &bintree{nil, map[string]*bintree{
"data": &bintree{nil, map[string]*bintree{
"github_auth.tmpl": &bintree{dataGithub_authTmpl, map[string]*bintree{}},
+ "github_auth_result.tmpl": &bintree{dataGithub_auth_resultTmpl, map[string]*bintree{}},
"google_auth.tmpl": &bintree{dataGoogle_authTmpl, map[string]*bintree{}},
}},
}}
diff --git a/auth_server/authn/data/github_auth.tmpl b/auth_server/authn/data/github_auth.tmpl
index 67529e99..4ec1afc6 100644
--- a/auth_server/authn/data/github_auth.tmpl
+++ b/auth_server/authn/data/github_auth.tmpl
@@ -1,6 +1,75 @@
-
+
+
+
+
+
+ Docker Registry Authentication
+
+
+
- Login with GitHub
- Revoke access
+
diff --git a/auth_server/authn/data/github_auth_result.tmpl b/auth_server/authn/data/github_auth_result.tmpl
new file mode 100644
index 00000000..df73761d
--- /dev/null
+++ b/auth_server/authn/data/github_auth_result.tmpl
@@ -0,0 +1,45 @@
+
+
+
+
+
+ Docker Registry Authentication
+
+
+
+
+ You are successfully authenticated for the Docker Registry{{if .Organization}} with the @{{.Organization}} Github organization{{end}}.
+ Use the following username and password to login into the registry:
+
+
+ $ docker login -u {{.Username}} -p {{.Password}} YOUR_REGISTRY_FQDN
+
+
diff --git a/auth_server/authn/github_auth.go b/auth_server/authn/github_auth.go
index 6a47aa53..364bafa1 100644
--- a/auth_server/authn/github_auth.go
+++ b/auth_server/authn/github_auth.go
@@ -31,6 +31,28 @@ import (
"github.com/cesanta/glog"
)
+type GitHubTeamCollection []GitHubTeam
+
+type GitHubTeam struct {
+ Id int64 `json:"id"`
+ Url string `json:"url,omitempty"`
+ Name string `json:"name,omitempty"`
+ Slug string `json:"slug,omitempty"`
+ Organization *GitHubOrganization `json:"organization"`
+ Parent *ParentGitHubTeam `json:"parent,omitempty"`
+}
+
+type GitHubOrganization struct {
+ Login string `json:"login"`
+ Id int64 `json:"id,omitempty"`
+}
+
+type ParentGitHubTeam struct {
+ Id int64 `json:"id"`
+ Name string `json:"name,omitempty"`
+ Slug string `json:"slug,omitempty"`
+}
+
type GitHubAuthConfig struct {
Organization string `yaml:"organization,omitempty"`
ClientId string `yaml:"client_id,omitempty"`
@@ -61,10 +83,77 @@ type GitHubTokenUser struct {
}
type GitHubAuth struct {
- config *GitHubAuthConfig
- db TokenDB
- client *http.Client
- tmpl *template.Template
+ config *GitHubAuthConfig
+ db TokenDB
+ client *http.Client
+ tmpl *template.Template
+ tmplResult *template.Template
+}
+
+type linkHeader struct {
+ First string
+ Last string
+ Next string
+ Prev string
+}
+
+func execGHExperimentalApiRequest(url string, token string) (*http.Response, error) {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ err = fmt.Errorf("could not create an http request for uri: %s. Error: %s", url, err)
+ return nil, err
+ }
+ req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
+ // Currently an "experimental" API; https://developer.github.com/v3/orgs/teams/#list-user-teams
+ req.Header.Add("Accept", "application/vnd.github.hellcat-preview+json")
+
+ client := &http.Client{Timeout: 10 * time.Second}
+ resp, err := client.Do(req)
+ if err != nil {
+ err = fmt.Errorf("HTTP error while retrieving %s. Error : %s", url, err)
+ return nil, err
+ }
+
+ return resp, nil
+}
+
+// removeSubstringsFromString removes all occurences of stringsToStrip from sourceStr
+//
+func removeSubstringsFromString(sourceStr string, stringsToStrip []string) string {
+ theNewString := sourceStr
+ for _, i := range stringsToStrip {
+ theNewString = strings.Replace(theNewString, i, "", -1)
+ }
+ return theNewString
+}
+
+// parseLinkHeader parses the HTTP headers from the Github API response
+//
+// https://developer.github.com/v3/guides/traversing-with-pagination/
+//
+func parseLinkHeader(linkLines []string) (linkHeader, error) {
+ var lH linkHeader
+ // URL in link is enclosed in < >
+ stringsToRemove := []string{"<", ">"}
+
+ for _, linkLine := range linkLines {
+ for _, linkItem := range strings.Split(linkLine, ",") {
+ linkData := strings.Split(linkItem, ";")
+ trimmedUrl := removeSubstringsFromString(strings.TrimSpace(linkData[0]), stringsToRemove)
+ linkVal := linkData[1]
+ switch {
+ case strings.Contains(linkVal, "first"):
+ lH.First = trimmedUrl
+ case strings.Contains(linkVal, "last"):
+ lH.Last = trimmedUrl
+ case strings.Contains(linkVal, "next"):
+ lH.Next = trimmedUrl
+ case strings.Contains(linkVal, "prev"):
+ lH.Prev = trimmedUrl
+ }
+ }
+ }
+ return lH, nil
}
func NewGitHubAuth(c *GitHubAuthConfig) (*GitHubAuth, error) {
@@ -83,19 +172,31 @@ func NewGitHubAuth(c *GitHubAuthConfig) (*GitHubAuth, error) {
}
glog.Infof("GitHub auth token DB at %s", dbName)
return &GitHubAuth{
- config: c,
- db: db,
- client: &http.Client{Timeout: 10 * time.Second},
- tmpl: template.Must(template.New("github_auth").Parse(string(MustAsset("data/github_auth.tmpl")))),
+ config: c,
+ db: db,
+ client: &http.Client{Timeout: 10 * time.Second},
+ tmpl: template.Must(template.New("github_auth").Parse(string(MustAsset("data/github_auth.tmpl")))),
+ tmplResult: template.Must(template.New("github_auth_result").Parse(string(MustAsset("data/github_auth_result.tmpl")))),
}, nil
}
func (gha *GitHubAuth) doGitHubAuthPage(rw http.ResponseWriter, req *http.Request) {
if err := gha.tmpl.Execute(rw, struct {
- ClientId, GithubWebUri string
+ ClientId, GithubWebUri, Organization string
}{
ClientId: gha.config.ClientId,
- GithubWebUri: gha.getGithubWebUri()}); err != nil {
+ GithubWebUri: gha.getGithubWebUri(),
+ Organization: gha.config.Organization}); err != nil {
+ http.Error(rw, fmt.Sprintf("Template error: %s", err), http.StatusInternalServerError)
+ }
+}
+
+func (gha *GitHubAuth) doGitHubAuthResultPage(rw http.ResponseWriter, username string, password string) {
+ if err := gha.tmplResult.Execute(rw, struct {
+ Organization, Username, Password string
+ }{Organization: gha.config.Organization,
+ Username: username,
+ Password: password}); err != nil {
http.Error(rw, fmt.Sprintf("Template error: %s", err), http.StatusInternalServerError)
}
}
@@ -172,10 +273,16 @@ func (gha *GitHubAuth) doGitHubAuthCreateToken(rw http.ResponseWriter, code stri
glog.Infof("New GitHub auth token for %s", user)
+ userTeams, err := gha.fetchTeams(c2t.AccessToken)
+ if err != nil {
+ glog.Errorf("could not fetch user teams: %s", err)
+ }
+
v := &TokenDBValue{
TokenType: c2t.TokenType,
AccessToken: c2t.AccessToken,
ValidUntil: time.Now().Add(gha.config.RevalidateAfter),
+ Labels: map[string][]string{"teams": userTeams},
}
dp, err := gha.db.StoreToken(user, v, true)
if err != nil {
@@ -184,10 +291,11 @@ func (gha *GitHubAuth) doGitHubAuthCreateToken(rw http.ResponseWriter, code stri
return
}
- fmt.Fprintf(rw, `Server logged in; now run "docker login YOUR_REGISTRY_FQDN", use %s as login and %s as password.`, user, dp)
+ gha.doGitHubAuthResultPage(rw, user, dp)
}
func (gha *GitHubAuth) validateAccessToken(token string) (user string, err error) {
+ glog.Infof("Github API: Fetching user info")
req, err := http.NewRequest("GET", fmt.Sprintf("%s/user", gha.getGithubApiUri()), nil)
if err != nil {
err = fmt.Errorf("could not create request to get information for token %s: %s", token, err)
@@ -225,6 +333,7 @@ func (gha *GitHubAuth) checkOrganization(token, user string) (err error) {
if gha.config.Organization == "" {
return nil
}
+ glog.Infof("Github API: Fetching organization membership info")
url := fmt.Sprintf("%s/orgs/%s/members/%s", gha.getGithubApiUri(), gha.config.Organization, user)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
@@ -242,7 +351,7 @@ func (gha *GitHubAuth) checkOrganization(token, user string) (err error) {
case http.StatusNoContent:
return nil
case http.StatusNotFound:
- return fmt.Errorf("%s is not a member of organization %s", user, gha.config.Organization)
+ return fmt.Errorf("user %s is not a member of organization %s", user, gha.config.Organization)
case http.StatusFound:
return fmt.Errorf("token %s could not get membership for organization %s", token, gha.config.Organization)
}
@@ -250,14 +359,83 @@ func (gha *GitHubAuth) checkOrganization(token, user string) (err error) {
return fmt.Errorf("Unknown status for membership of organization %s: %s", gha.config.Organization, resp.Status)
}
+func (gha *GitHubAuth) fetchTeams(token string) ([]string, error) {
+ var allTeams GitHubTeamCollection
+
+ if gha.config.Organization == "" {
+ return nil, nil
+ }
+ glog.Infof("Github API: Fetching user teams")
+ url := fmt.Sprintf("%s/user/teams?per_page=100", gha.getGithubApiUri())
+ var err error
+
+ // Using an `i` iterator for debugging the results
+ for i := 1; url != ""; i++ {
+ var pagedTeams GitHubTeamCollection
+ resp, err := execGHExperimentalApiRequest(url, token)
+ if err != nil {
+ return nil, err
+ }
+
+ respHeaders := resp.Header
+ body, _ := ioutil.ReadAll(resp.Body)
+ resp.Body.Close()
+
+ err = json.Unmarshal(body, &pagedTeams)
+ if err != nil {
+ err = fmt.Errorf("Error parsing the JSON response while fetching teams: %s", err)
+ return nil, err
+ }
+
+ allTeams = append(allTeams, pagedTeams...)
+
+ // Do we need to paginate?
+ if link, ok := respHeaders["Link"]; ok {
+ parsedLink, _ := parseLinkHeader(link)
+ url = parsedLink.Next
+ glog.V(2).Infof("--> Page <%d>\n", i)
+ } else {
+ url = ""
+ }
+ }
+
+ // Use map instead of slice to ensure uniqueness of results
+ organizationTeamsMap := make(map[string]bool)
+ for _, item := range allTeams {
+ if item.Organization.Login == gha.config.Organization {
+ organizationTeamsMap[item.Slug] = true
+ if item.Parent != nil {
+ organizationTeamsMap[item.Parent.Slug] = true
+ }
+ }
+ }
+
+ organizationTeams := make([]string, len(organizationTeamsMap))
+ i := 0
+ for orgTeam, _ := range organizationTeamsMap {
+ organizationTeams[i] = orgTeam
+ i++
+ }
+
+ glog.V(3).Infof("All teams for the user: %v", allTeams)
+ glog.Infof("Teams for the <%s> organization: %v", gha.config.Organization, organizationTeams)
+ return organizationTeams, err
+}
+
func (gha *GitHubAuth) validateServerToken(user string) (*TokenDBValue, error) {
v, err := gha.db.GetValue(user)
if err != nil || v == nil {
if err == nil {
- err = errors.New("no db value, please sign out and sign in again.")
+ err = errors.New("no db value, please sign out and sign in again")
}
return nil, err
}
+
+ texp := v.ValidUntil.Sub(time.Now())
+ glog.V(3).Infof("Existing GitHub auth token for <%s> expires after: <%d> sec", user, int(texp.Seconds()))
+
+ glog.V(1).Infof("Token has expired. I will revalidate the access token.")
+ glog.V(3).Infof("Old token is: %+v", v)
tokenUser, err := gha.validateAccessToken(v.AccessToken)
if err != nil {
glog.Warningf("Token for %q failed validation: %s", user, err)
@@ -267,9 +445,21 @@ func (gha *GitHubAuth) validateServerToken(user string) (*TokenDBValue, error) {
glog.Errorf("token for wrong user: expected %s, found %s", user, tokenUser)
return nil, fmt.Errorf("found token for wrong user")
}
+
+ // Update revalidation timestamp
v.ValidUntil = time.Now().Add(gha.config.RevalidateAfter)
- texp := v.ValidUntil.Sub(time.Now())
- glog.V(1).Infof("Validated GitHub auth token for %s (exp %d)", user, int(texp.Seconds()))
+ glog.V(3).Infof("New token is: %+v", v)
+
+ // Update token
+ _, err = gha.db.StoreToken(user, v, false)
+ if err != nil {
+ glog.Errorf("Failed to record server token: %s", err)
+ return nil, fmt.Errorf("Unable to store renewed token expiry time: %s", err)
+ }
+ glog.V(2).Infof("Successfully revalidated token")
+
+ texp = v.ValidUntil.Sub(time.Now())
+ glog.V(3).Infof("Re-validated GitHub auth token for %s. Next revalidation in %dsec.", user, int64(texp.Seconds()))
return v, nil
}
@@ -283,7 +473,16 @@ func (gha *GitHubAuth) Authenticate(user string, password PasswordString) (bool,
} else if err != nil {
return false, nil, err
}
- return true, nil, nil
+
+ v, err := gha.db.GetValue(user)
+ if err != nil || v == nil {
+ if err == nil {
+ err = errors.New("no db value, please sign out and sign in again")
+ }
+ return false, nil, err
+ }
+
+ return true, v.Labels, nil
}
func (gha *GitHubAuth) Stop() {
diff --git a/auth_server/server/server.go b/auth_server/server/server.go
index 345facbb..5d2d6aee 100644
--- a/auth_server/server/server.go
+++ b/auth_server/server/server.go
@@ -350,13 +350,17 @@ func (as *AuthServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// https://developers.google.com/identity/sign-in/web/server-side-flow
func (as *AuthServer) doIndex(rw http.ResponseWriter, req *http.Request) {
- rw.Header().Set("Content-Type", "text/html; charset=utf-8")
- fmt.Fprintf(rw, "%s \n", as.config.Token.Issuer)
- if as.ga != nil {
- fmt.Fprint(rw, `Login with Google account
`)
- }
- if as.gha != nil {
- fmt.Fprint(rw, `Login with GitHub account
`)
+ switch {
+ case as.ga != nil:
+ rw.Header().Set("Content-Type", "text/html; charset=utf-8")
+ fmt.Fprintf(rw, "%s \n", as.config.Token.Issuer)
+ fmt.Fprint(rw, `Login with Google account
`)
+ case as.gha != nil:
+ url := as.config.Server.PathPrefix + "/github_auth"
+ http.Redirect(rw, req, url, 301)
+ default:
+ rw.Header().Set("Content-Type", "text/html; charset=utf-8")
+ fmt.Fprintf(rw, "%s \n", as.config.Token.Issuer)
}
}
From 335f36b35872304c92e85429b6fcd0327ffca051 Mon Sep 17 00:00:00 2001
From: Adam Shannon
Date: Thu, 31 May 2018 20:41:57 -0500
Subject: [PATCH 011/128] doc: mention make deps in auth_server README (#233)
---
auth_server/README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/auth_server/README.md b/auth_server/README.md
index 34b203fb..2a3d8cdd 100644
--- a/auth_server/README.md
+++ b/auth_server/README.md
@@ -8,5 +8,8 @@ pip install gitpython
mkdir /var/tmp/go
export GOPATH=/var/tmp/go
export PATH=$PATH:$GOPATH/bin
+# download dependencies
+make deps
+# build source
make
```
From fb1183af2d98345aba03474d64aca7e16a5b6bbf Mon Sep 17 00:00:00 2001
From: Adam Shannon
Date: Thu, 31 May 2018 20:42:33 -0500
Subject: [PATCH 012/128] drop hardcoded configs around TLS 1.x (#232)
Modern Go versions (1.9 and 1.10) as of this commit are much better
about cipher suite selection and the ssl/tls protocols used. In fact,
SSLv3 needs to be explicitly enabled now.
Fixes: https://github.com/cesanta/docker_auth/issues/231
---
auth_server/main.go | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/auth_server/main.go b/auth_server/main.go
index aa0056bf..eb1f0eb8 100644
--- a/auth_server/main.go
+++ b/auth_server/main.go
@@ -50,19 +50,7 @@ func ServeOnce(c *server.Config, cf string, hd *httpdown.HTTP) (*server.AuthServ
}
tlsConfig := &tls.Config{
- MinVersion: tls.VersionTLS10,
PreferServerCipherSuites: true,
- CipherSuites: []uint16{
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
- tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- tls.TLS_RSA_WITH_AES_128_CBC_SHA,
- tls.TLS_RSA_WITH_AES_256_CBC_SHA,
- },
- NextProtos: []string{"http/1.1"},
}
if c.Server.CertFile != "" || c.Server.KeyFile != "" {
// Check for partial configuration.
From 4bd401c0a07a8e2d5807af1f1995cf114a8c5e16 Mon Sep 17 00:00:00 2001
From: Karel Minarik
Date: Fri, 1 Jun 2018 16:15:49 +0200
Subject: [PATCH 013/128] [authn] Added the `Labels` property to the
`TokenDBValue` struct (#217)
This patch extends the `TokenDBValue` struct in order to allow storing "labels" associated
with a user directly in the data structure, ie. without a need for secondary storage.
The primary motivation is related to the possibility of storing user's Github teams
as `labels`, and using them in the ACL configuration.
---
auth_server/authn/tokendb.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/auth_server/authn/tokendb.go b/auth_server/authn/tokendb.go
index 6f650c57..96b37ec6 100644
--- a/auth_server/authn/tokendb.go
+++ b/auth_server/authn/tokendb.go
@@ -70,6 +70,7 @@ type TokenDBValue struct {
// DockerPassword is the temporary password we use to authenticate Docker users.
// Generated at the time of token creation, stored here as a BCrypt hash.
DockerPassword string `json:"docker_password,omitempty"`
+ Labels Labels `json:"labels,omitempty"`
}
// NewTokenDB returns a new TokenDB structure
From 3420ca1aaf63bbdfe9bfe47d9c2eab6482b5a9d2 Mon Sep 17 00:00:00 2001
From: rojer
Date: Fri, 1 Jun 2018 15:23:09 +0100
Subject: [PATCH 014/128] Drop the -i, it's cleaner
Newer versions of Go do not need -i to not rebuild everything
---
auth_server/Makefile | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/auth_server/Makefile b/auth_server/Makefile
index 50f487b3..bcd0c6e5 100644
--- a/auth_server/Makefile
+++ b/auth_server/Makefile
@@ -4,7 +4,7 @@ COMPRESS_BINARY ?= false
CA_BUNDLE = /etc/ssl/certs/ca-certificates.crt
VERSION = $(shell cat version.txt)
-BUILDER_IMAGE ?= golang:1.8.0-alpine
+BUILDER_IMAGE ?= golang:1.10.0-alpine
.PHONY: %
@@ -24,7 +24,7 @@ generate:
github.com/cesanta/docker_auth/auth_server/server/...
build:
- CGO_ENABLED=0 go build -v -i --ldflags=--s
+ CGO_ENABLED=0 go build -v --ldflags=--s
ca-certificates.crt:
cp $(CA_BUNDLE) .
@@ -38,8 +38,8 @@ build-release: ca-certificates.crt
umask 0 && govendor sync -v && \
go install -v github.com/cesanta/docker_auth/auth_server/vendor/github.com/jteeuwen/go-bindata/go-bindata && \
make generate && \
- CGO_ENABLED=0 go build -v -i --ldflags=--s"
- @echo === Built version $(VERSION) ===
+ CGO_ENABLED=0 go build -v --ldflags=--s"
+ @echo === Built version $$(cat version.txt) ===
auth_server:
@echo
From e10780b62fb04d6dc0f9012a39a5afe46755a308 Mon Sep 17 00:00:00 2001
From: Adam Shannon
Date: Fri, 1 Jun 2018 10:11:10 -0500
Subject: [PATCH 015/128] docs: quick setup steps for github auth (#234)
---
README.md | 1 +
docs/auth-methods.md | 26 ++++++++++++++++++++++++++
2 files changed, 27 insertions(+)
create mode 100644 docs/auth-methods.md
diff --git a/README.md b/README.md
index 85813f79..dec6f38e 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ This server fills the gap and implements the protocol described [here](https://g
Supported authentication methods:
* Static list of users
* Google Sign-In (incl. Google for Work / GApps for domain) (documented [here](https://github.com/cesanta/docker_auth/blob/master/examples/reference.yml))
+ * [Github Sign-In](docs/auth-methods.md#github)
* LDAP bind ([demo](https://github.com/kwk/docker-registry-setup))
* MongoDB user collection
* [External program](https://github.com/cesanta/docker_auth/blob/master/examples/ext_auth.sh)
diff --git a/docs/auth-methods.md b/docs/auth-methods.md
new file mode 100644
index 00000000..f95f4025
--- /dev/null
+++ b/docs/auth-methods.md
@@ -0,0 +1,26 @@
+## Github
+
+First you need to setup a [Github OAuth Application](https://github.com/settings/applications).
+
+- The callback url needs to be `$fqdn:5001/github_auth`
+ - `$fqdn` is the domain where docker_auth is accessed
+ - `5001` or what port is specified in the `server` block
+
+Once you have setup a Github OAuth application you need to add a `github` block to the docker_auth config file:
+
+```yaml
+github_auth:
+ organization: "my-org-name"
+ client_id: "..."
+ client_secret: "..." # or client_secret_file
+ token_db: /data/tokens.db
+```
+
+Then specify what teams can do via acls
+
+```yaml
+acl:
+ - match: {team: "infrastructure"}
+ actions: ["pull", "push"]
+ comment: "Infrastructure team members can push and all images"
+```
From b1fb3677cf290a9d4b19f7c50aced0a35ba05fb3 Mon Sep 17 00:00:00 2001
From: Karel Minarik
Date: Fri, 20 Jul 2018 15:45:18 +0200
Subject: [PATCH 016/128] [authn] Added the ability to display full `docker
login` command (#221)
In order to make the `docker login` command more user-friendly,
allow to set `registry_url` in the YAML configuration, and
display it when the user has been succesfully authenticated.
If the parameter is not set, display an example URL.
(cherry picked from commit 3aeb8476dcb5b208cc3aac4d64bf1c10c020daf4)
---
auth_server/authn/bindata.go | 10 ++++++++--
auth_server/authn/data/github_auth_result.tmpl | 8 +++++++-
auth_server/authn/github_auth.go | 8 +++++---
examples/reference.yml | 2 ++
4 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/auth_server/authn/bindata.go b/auth_server/authn/bindata.go
index 13f07a6a..a37fc695 100644
--- a/auth_server/authn/bindata.go
+++ b/auth_server/authn/bindata.go
@@ -172,6 +172,12 @@ var _dataGithub_auth_resultTmpl = []byte(`
border-radius: 0.5em;
text-shadow: 0px 1px 0px #fff;
}
+ .command span {
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ }
@@ -180,7 +186,7 @@ var _dataGithub_auth_resultTmpl = []byte(`
Use the following username and password to login into the registry:
- $ docker login -u {{.Username}} -p {{.Password}} YOUR_REGISTRY_FQDN
+ $ docker login -u {{.Username}} -p {{.Password}} {{if .RegistryUrl}}{{.RegistryUrl}}{{else}}docker.example.com{{end}}
`)
@@ -195,7 +201,7 @@ func dataGithub_auth_resultTmpl() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "data/github_auth_result.tmpl", size: 1094, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
+ info := bindataFileInfo{name: "data/github_auth_result.tmpl", size: 1300, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
diff --git a/auth_server/authn/data/github_auth_result.tmpl b/auth_server/authn/data/github_auth_result.tmpl
index df73761d..2619d0cd 100644
--- a/auth_server/authn/data/github_auth_result.tmpl
+++ b/auth_server/authn/data/github_auth_result.tmpl
@@ -32,6 +32,12 @@
border-radius: 0.5em;
text-shadow: 0px 1px 0px #fff;
}
+ .command span {
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ }
@@ -40,6 +46,6 @@
Use the following username and password to login into the registry:
- $ docker login -u {{.Username}} -p {{.Password}} YOUR_REGISTRY_FQDN
+ $ docker login -u {{.Username}} -p {{.Password}} {{if .RegistryUrl}}{{.RegistryUrl}}{{else}}docker.example.com{{end}}