Support admin group in http authproxy

This commit adds admin_groups into the configuration of http_auth
settings, it's a string in the form of "group1, group2".  If the token
review result shows the user is in one of the groups in the setting he
will have the administrator role in Harbor.

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2020-12-09 20:25:58 +08:00
parent ec2f251d63
commit 60e3668d43
10 changed files with 109 additions and 25 deletions

View File

@ -129,6 +129,7 @@ var (
{Name: common.HTTPAuthProxyEndpoint, Scope: UserScope, Group: HTTPAuthGroup, ItemType: &StringType{}},
{Name: common.HTTPAuthProxyTokenReviewEndpoint, Scope: UserScope, Group: HTTPAuthGroup, ItemType: &StringType{}},
{Name: common.HTTPAuthProxyAdminGroups, Scope: UserScope, Group: HTTPAuthGroup, ItemType: &StringType{}},
{Name: common.HTTPAuthProxyVerifyCert, Scope: UserScope, Group: HTTPAuthGroup, DefaultValue: "true", ItemType: &BoolType{}},
{Name: common.HTTPAuthProxySkipSearch, Scope: UserScope, Group: HTTPAuthGroup, DefaultValue: "false", ItemType: &BoolType{}},
{Name: common.HTTPAuthProxyServerCertificate, Scope: UserScope, Group: HTTPAuthGroup, ItemType: &StringType{}},

View File

@ -96,6 +96,7 @@ const (
UAAVerifyCert = "uaa_verify_cert"
HTTPAuthProxyEndpoint = "http_authproxy_endpoint"
HTTPAuthProxyTokenReviewEndpoint = "http_authproxy_tokenreview_endpoint"
HTTPAuthProxyAdminGroups = "http_authproxy_admin_groups"
HTTPAuthProxyVerifyCert = "http_authproxy_verify_cert"
HTTPAuthProxySkipSearch = "http_authproxy_skip_search"
HTTPAuthProxyServerCertificate = "http_authproxy_server_certificate"

View File

@ -69,11 +69,12 @@ type Email struct {
// HTTPAuthProxy wraps the settings for HTTP auth proxy
type HTTPAuthProxy struct {
Endpoint string `json:"endpoint"`
TokenReviewEndpoint string `json:"tokenreivew_endpoint"`
VerifyCert bool `json:"verify_cert"`
SkipSearch bool `json:"skip_search"`
ServerCertificate string `json:"server_certificate"`
Endpoint string `json:"endpoint"`
TokenReviewEndpoint string `json:"tokenreivew_endpoint"`
AdminGroups []string `json:"admin_groups"`
VerifyCert bool `json:"verify_cert"`
SkipSearch bool `json:"skip_search"`
ServerCertificate string `json:"server_certificate"`
}
// OIDCSetting wraps the settings for OIDC auth endpoint

View File

@ -116,7 +116,7 @@ func (a *Auth) tokenReview(sessionID string) (*models.User, error) {
if err != nil {
return nil, err
}
u, err := authproxy.UserFromReviewStatus(reviewStatus)
u, err := authproxy.UserFromReviewStatus(reviewStatus, httpAuthProxySetting.AdminGroups)
if err != nil {
return nil, err
}

View File

@ -391,6 +391,7 @@ func HTTPAuthProxySetting() (*models.HTTPAuthProxy, error) {
return &models.HTTPAuthProxy{
Endpoint: cfgMgr.Get(common.HTTPAuthProxyEndpoint).GetString(),
TokenReviewEndpoint: cfgMgr.Get(common.HTTPAuthProxyTokenReviewEndpoint).GetString(),
AdminGroups: splitAndTrim(cfgMgr.Get(common.HTTPAuthProxyAdminGroups).GetString(), ","),
VerifyCert: cfgMgr.Get(common.HTTPAuthProxyVerifyCert).GetBool(),
SkipSearch: cfgMgr.Get(common.HTTPAuthProxySkipSearch).GetBool(),
ServerCertificate: cfgMgr.Get(common.HTTPAuthProxyServerCertificate).GetString(),
@ -405,11 +406,7 @@ func OIDCSetting() (*models.OIDCSetting, error) {
}
scopeStr := cfgMgr.Get(common.OIDCScope).GetString()
extEndpoint := strings.TrimSuffix(cfgMgr.Get(common.ExtEndpoint).GetString(), "/")
scope := []string{}
for _, s := range strings.Split(scopeStr, ",") {
scope = append(scope, strings.TrimSpace(s))
}
scope := splitAndTrim(scopeStr, ",")
return &models.OIDCSetting{
Name: cfgMgr.Get(common.OIDCName).GetString(),
Endpoint: cfgMgr.Get(common.OIDCEndpoint).GetString(),
@ -479,3 +476,13 @@ func Metric() *models.Metric {
Path: cfgMgr.Get(common.MetricPath).GetString(),
}
}
func splitAndTrim(s, sep string) []string {
res := make([]string, 0)
for _, s := range strings.Split(s, sep) {
if e := strings.TrimSpace(s); len(e) > 0 {
res = append(res, e)
}
}
return res
}

View File

@ -238,6 +238,7 @@ y1bQusZMygQezfCuEzsewF+OpANFovCTUEs6s5vyoVNP8lk=
m := map[string]interface{}{
common.HTTPAuthProxySkipSearch: "true",
common.HTTPAuthProxyVerifyCert: "true",
common.HTTPAuthProxyAdminGroups: "group1, group2",
common.HTTPAuthProxyEndpoint: "https://auth.proxy/suffix",
common.HTTPAuthProxyServerCertificate: certificate,
}
@ -246,6 +247,7 @@ y1bQusZMygQezfCuEzsewF+OpANFovCTUEs6s5vyoVNP8lk=
assert.Nil(t, e)
assert.Equal(t, *v, models.HTTPAuthProxy{
Endpoint: "https://auth.proxy/suffix",
AdminGroups: []string{"group1", "group2"},
SkipSearch: true,
VerifyCert: true,
ServerCertificate: certificate,
@ -279,3 +281,35 @@ func TestOIDCSetting(t *testing.T) {
assert.ElementsMatch(t, []string{"openid", "profile"}, v.Scope)
assert.Equal(t, "username", v.UserClaim)
}
func TestSplitAndTrim(t *testing.T) {
cases := []struct {
s string
sep string
expect []string
}{
{
s: "abc",
sep: ",",
expect: []string{"abc"},
},
{
s: "a, b, c",
sep: ",",
expect: []string{"a", "b", "c"},
},
{
s: "a,b,c ",
sep: ".",
expect: []string{"a,b,c"},
},
{
s: "",
sep: ",",
expect: []string{},
},
}
for _, c := range cases {
assert.Equal(t, c.expect, splitAndTrim(c.s, c.sep))
}
}

View File

@ -79,7 +79,8 @@ func getTLSConfig(config *models.HTTPAuthProxy) rest.TLSClientConfig {
// UserFromReviewStatus transform a review status to a user model.
// Group entries will be populated if needed.
func UserFromReviewStatus(status k8s_api_v1beta1.TokenReviewStatus) (*models.User, error) {
func UserFromReviewStatus(status k8s_api_v1beta1.TokenReviewStatus, adminGroups []string) (*models.User, error) {
if !status.Authenticated {
return nil, fmt.Errorf("failed to authenticate the token, error in status: %s", status.Error)
}
@ -94,6 +95,18 @@ func UserFromReviewStatus(status k8s_api_v1beta1.TokenReviewStatus) (*models.Use
}
log.Debugf("current user's group ID list is %+v", groupIDList)
user.GroupIDs = groupIDList
if len(adminGroups) > 0 {
agm := make(map[string]struct{})
for _, ag := range adminGroups {
agm[ag] = struct{}{}
}
for _, ug := range status.User.Groups {
if _, ok := agm[ug]; ok {
user.AdminRoleInAuth = true
break
}
}
}
}
return user, nil

View File

@ -21,19 +21,22 @@ func TestMain(m *testing.M) {
func TestUserFromReviewStatus(t *testing.T) {
type result struct {
hasErr bool
username string
groupLen int
hasErr bool
username string
groupLen int
adminInAuth bool
}
cases := []struct {
input v1beta1.TokenReviewStatus
expect result
input v1beta1.TokenReviewStatus
adminGroups []string
expect result
}{
{
input: v1beta1.TokenReviewStatus{
Authenticated: false,
Error: "connection error",
},
adminGroups: []string{"admin"},
expect: result{
hasErr: true,
},
@ -46,10 +49,12 @@ func TestUserFromReviewStatus(t *testing.T) {
UID: "u-1",
},
},
adminGroups: []string{"admin"},
expect: result{
hasErr: false,
username: "jack",
groupLen: 0,
hasErr: false,
username: "jack",
groupLen: 0,
adminInAuth: false,
},
},
{
@ -61,21 +66,41 @@ func TestUserFromReviewStatus(t *testing.T) {
},
Error: "",
},
adminGroups: []string{"group2", "admin"},
expect: result{
hasErr: false,
username: "daniel",
groupLen: 2,
hasErr: false,
username: "daniel",
groupLen: 2,
adminInAuth: true,
},
},
{
input: v1beta1.TokenReviewStatus{
Authenticated: true,
User: v1beta1.UserInfo{
Username: "daniel",
Groups: []string{"group1", "group2"},
},
Error: "",
},
adminGroups: []string{},
expect: result{
hasErr: false,
username: "daniel",
groupLen: 2,
adminInAuth: false,
},
},
}
for _, c := range cases {
u, err := UserFromReviewStatus(c.input)
u, err := UserFromReviewStatus(c.input, c.adminGroups)
if c.expect.hasErr == true {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
assert.Equal(t, c.expect.username, u.Username)
assert.Equal(t, c.expect.groupLen, len(u.GroupIDs))
assert.Equal(t, c.expect.adminInAuth, u.AdminRoleInAuth)
}
if u != nil {
for _, gid := range u.GroupIDs {

View File

@ -86,12 +86,13 @@ func (a *authProxy) Generate(req *http.Request) security.Context {
return nil
}
}
u2, err := authproxy.UserFromReviewStatus(tokenReviewStatus)
u2, err := authproxy.UserFromReviewStatus(tokenReviewStatus, httpAuthProxyConf.AdminGroups)
if err != nil {
log.Errorf("failed to get user information from token review status: %v", err)
return nil
}
user.GroupIDs = u2.GroupIDs
user.AdminRoleInAuth = u2.AdminRoleInAuth
log.Debugf("an auth proxy security context generated for request %s %s", req.Method, req.URL.Path)
return local.NewSecurityContext(user)
}

View File

@ -57,6 +57,7 @@ func TestAuthProxy(t *testing.T) {
SkipSearch: true,
VerifyCert: false,
TokenReviewEndpoint: server.URL,
AdminGroups: []string{},
})
// No onboard