mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-23 07:11:36 +01:00
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:
parent
ec2f251d63
commit
60e3668d43
@ -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{}},
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ func TestAuthProxy(t *testing.T) {
|
||||
SkipSearch: true,
|
||||
VerifyCert: false,
|
||||
TokenReviewEndpoint: server.URL,
|
||||
AdminGroups: []string{},
|
||||
})
|
||||
|
||||
// No onboard
|
||||
|
Loading…
Reference in New Issue
Block a user