mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 20:26:13 +01:00
Merge pull request #4592 from reasonerjt/secret-in-header
Store secret in header instead of cookie
This commit is contained in:
commit
9474a9773e
@ -15,6 +15,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/vmware/harbor/src/common/secret"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,21 +42,10 @@ func (s *secretAuthenticator) Authenticate(req *http.Request) (bool, error) {
|
|||||||
if len(s.secrets) == 0 {
|
if len(s.secrets) == 0 {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
reqSecret := secret.FromRequest(req)
|
||||||
secret, err := req.Cookie("secret")
|
|
||||||
if err != nil {
|
|
||||||
if err == http.ErrNoCookie {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if secret == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range s.secrets {
|
for _, v := range s.secrets {
|
||||||
if secret.Value == v {
|
if reqSecret == v {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
commonsecret "github.com/vmware/harbor/src/common/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthenticate(t *testing.T) {
|
func TestAuthenticate(t *testing.T) {
|
||||||
@ -32,11 +33,7 @@ func TestAuthenticate(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create request: %v", err)
|
t.Fatalf("failed to create request: %v", err)
|
||||||
}
|
}
|
||||||
req2.AddCookie(&http.Cookie{
|
_ = commonsecret.AddToRequest(req2, secret)
|
||||||
Name: "secret",
|
|
||||||
Value: secret,
|
|
||||||
})
|
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
secrets map[string]string
|
secrets map[string]string
|
||||||
req *http.Request
|
req *http.Request
|
||||||
|
@ -19,10 +19,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/http/modifier"
|
"github.com/vmware/harbor/src/common/http/modifier"
|
||||||
)
|
"github.com/vmware/harbor/src/common/secret"
|
||||||
|
|
||||||
const (
|
|
||||||
secretCookieName = "secret"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Authorizer is a kind of Modifier used to authorize the requests
|
// Authorizer is a kind of Modifier used to authorize the requests
|
||||||
@ -45,10 +42,6 @@ func (s *SecretAuthorizer) Modify(req *http.Request) error {
|
|||||||
if req == nil {
|
if req == nil {
|
||||||
return errors.New("the request is null")
|
return errors.New("the request is null")
|
||||||
}
|
}
|
||||||
|
secret.AddToRequest(req, s.secret)
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: secretCookieName,
|
|
||||||
Value: s.secret,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
commonsecret "github.com/vmware/harbor/src/common/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthorizeOfSecretAuthorizer(t *testing.T) {
|
func TestAuthorizeOfSecretAuthorizer(t *testing.T) {
|
||||||
@ -32,8 +33,5 @@ func TestAuthorizeOfSecretAuthorizer(t *testing.T) {
|
|||||||
req, err := http.NewRequest("", "", nil)
|
req, err := http.NewRequest("", "", nil)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, authorizer.Modify(req))
|
require.Nil(t, authorizer.Modify(req))
|
||||||
require.Equal(t, 1, len(req.Cookies()))
|
assert.Equal(t, secret, commonsecret.FromRequest(req))
|
||||||
v, err := req.Cookie(secretCookieName)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assert.Equal(t, secret, v.Value)
|
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,6 @@ const (
|
|||||||
RepOpDelete string = "delete"
|
RepOpDelete string = "delete"
|
||||||
//RepOpSchedule represents the operation of a job to schedule the real replication process
|
//RepOpSchedule represents the operation of a job to schedule the real replication process
|
||||||
RepOpSchedule string = "schedule"
|
RepOpSchedule string = "schedule"
|
||||||
//UISecretCookie is the cookie name to contain the UI secret
|
|
||||||
UISecretCookie string = "secret"
|
|
||||||
//RepTargetTable is the table name for replication targets
|
//RepTargetTable is the table name for replication targets
|
||||||
RepTargetTable = "replication_target"
|
RepTargetTable = "replication_target"
|
||||||
//RepJobTable is the table name for replication jobs
|
//RepJobTable is the table name for replication jobs
|
||||||
|
47
src/common/secret/request.go
Normal file
47
src/common/secret/request.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//HeaderPrefix is the prefix of the value of Authorization header.
|
||||||
|
//It has the space.
|
||||||
|
const HeaderPrefix = "Harbor-Secret "
|
||||||
|
|
||||||
|
//FromRequest tries to get Harbor Secret from request header.
|
||||||
|
//It will return empty string if the reqeust is nil.
|
||||||
|
func FromRequest(req *http.Request) string {
|
||||||
|
if req == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
auth := req.Header.Get("Authorization")
|
||||||
|
if strings.HasPrefix(auth, HeaderPrefix) {
|
||||||
|
return strings.TrimPrefix(auth, HeaderPrefix)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddToRequest add the secret to request
|
||||||
|
func AddToRequest(req *http.Request, secret string) error {
|
||||||
|
if req == nil {
|
||||||
|
return fmt.Errorf("input request is nil, unable to set secret")
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("%s%s", HeaderPrefix, secret))
|
||||||
|
return nil
|
||||||
|
}
|
49
src/common/secret/request_test.go
Normal file
49
src/common/secret/request_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
rc := m.Run()
|
||||||
|
if rc != 0 {
|
||||||
|
os.Exit(rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromRequest(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
secret := "mysecret"
|
||||||
|
req, _ := http.NewRequest("GET", "http://test.com", nil)
|
||||||
|
req.Header.Add("Authorization", "Harbor-Secret "+secret)
|
||||||
|
assert.Equal(secret, FromRequest(req))
|
||||||
|
assert.Equal("", FromRequest(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddToRequest(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
secret := "mysecret"
|
||||||
|
req, _ := http.NewRequest("GET", "http://test.com", nil)
|
||||||
|
err := AddToRequest(req, secret)
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(secret, FromRequest(req))
|
||||||
|
assert.NotNil(AddToRequest(nil, secret))
|
||||||
|
}
|
@ -46,25 +46,3 @@ func (b *basicAuthCredential) Modify(req *http.Request) error {
|
|||||||
b.AddAuthorization(req)
|
b.AddAuthorization(req)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type cookieCredential struct {
|
|
||||||
cookie *http.Cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCookieCredential initialize a cookie based crendential handler, the cookie in parameter will be added to request to registry
|
|
||||||
// if this crendential is attached to a registry client.
|
|
||||||
func NewCookieCredential(c *http.Cookie) Credential {
|
|
||||||
return &cookieCredential{
|
|
||||||
cookie: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cookieCredential) AddAuthorization(req *http.Request) {
|
|
||||||
req.AddCookie(c.cookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
// implement github.com/vmware/harbor/src/common/http/modifier.Modifier
|
|
||||||
func (c *cookieCredential) Modify(req *http.Request) error {
|
|
||||||
c.AddAuthorization(req)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -41,26 +41,3 @@ func TestAddAuthorizationOfBasicAuthCredential(t *testing.T) {
|
|||||||
t.Errorf("unexpected password: %s != pwd", pwd)
|
t.Errorf("unexpected password: %s != pwd", pwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddAuthorizationOfCookieCredential(t *testing.T) {
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: "name",
|
|
||||||
Value: "value",
|
|
||||||
}
|
|
||||||
cred := NewCookieCredential(cookie)
|
|
||||||
req, err := http.NewRequest("GET", "http://example.com", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cred.Modify(req)
|
|
||||||
|
|
||||||
ck, err := req.Cookie("name")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to get cookie: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ck.Value != "value" {
|
|
||||||
t.Errorf("unexpected value: %s != value", ck.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,9 +5,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
common_http "github.com/vmware/harbor/src/common/http"
|
common_http "github.com/vmware/harbor/src/common/http"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/http/modifier/auth"
|
||||||
reg "github.com/vmware/harbor/src/common/utils/registry"
|
reg "github.com/vmware/harbor/src/common/utils/registry"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
|
||||||
"github.com/vmware/harbor/src/jobservice/env"
|
"github.com/vmware/harbor/src/jobservice/env"
|
||||||
"github.com/vmware/harbor/src/jobservice/logger"
|
"github.com/vmware/harbor/src/jobservice/logger"
|
||||||
)
|
)
|
||||||
@ -57,10 +56,7 @@ func (r *Replicator) init(ctx env.JobContext, params map[string]interface{}) err
|
|||||||
r.policyID = (int64)(params["policy_id"].(float64))
|
r.policyID = (int64)(params["policy_id"].(float64))
|
||||||
r.url = params["url"].(string)
|
r.url = params["url"].(string)
|
||||||
r.insecure = params["insecure"].(bool)
|
r.insecure = params["insecure"].(bool)
|
||||||
cred := auth.NewCookieCredential(&http.Cookie{
|
cred := auth.NewSecretAuthorizer(secret())
|
||||||
Name: models.UISecretCookie,
|
|
||||||
Value: secret(),
|
|
||||||
})
|
|
||||||
|
|
||||||
r.client = common_http.NewClient(&http.Client{
|
r.client = common_http.NewClient(&http.Client{
|
||||||
Transport: reg.GetHTTPTransport(r.insecure),
|
Transport: reg.GetHTTPTransport(r.insecure),
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
common_http "github.com/vmware/harbor/src/common/http"
|
common_http "github.com/vmware/harbor/src/common/http"
|
||||||
"github.com/vmware/harbor/src/common/http/modifier"
|
"github.com/vmware/harbor/src/common/http/modifier"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
httpauth "github.com/vmware/harbor/src/common/http/modifier/auth"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
reg "github.com/vmware/harbor/src/common/utils/registry"
|
reg "github.com/vmware/harbor/src/common/utils/registry"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||||
@ -108,10 +108,7 @@ func (t *Transfer) init(ctx env.JobContext, params map[string]interface{}) error
|
|||||||
// init source registry client
|
// init source registry client
|
||||||
srcURL := params["src_registry_url"].(string)
|
srcURL := params["src_registry_url"].(string)
|
||||||
srcInsecure := params["src_registry_insecure"].(bool)
|
srcInsecure := params["src_registry_insecure"].(bool)
|
||||||
srcCred := auth.NewCookieCredential(&http.Cookie{
|
srcCred := httpauth.NewSecretAuthorizer(secret())
|
||||||
Name: models.UISecretCookie,
|
|
||||||
Value: secret(),
|
|
||||||
})
|
|
||||||
srcTokenServiceURL := ""
|
srcTokenServiceURL := ""
|
||||||
if stsu, ok := params["src_token_service_url"]; ok {
|
if stsu, ok := params["src_token_service_url"]; ok {
|
||||||
srcTokenServiceURL = stsu.(string)
|
srcTokenServiceURL = stsu.(string)
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/auth/token"
|
"github.com/docker/distribution/registry/auth/token"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
httpauth "github.com/vmware/harbor/src/common/http/modifier/auth"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry"
|
"github.com/vmware/harbor/src/common/utils/registry"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
||||||
)
|
)
|
||||||
@ -33,11 +33,7 @@ func NewRepositoryClient(endpoint string, insecure bool, credential auth.Credent
|
|||||||
// access the internal registry
|
// access the internal registry
|
||||||
func NewRepositoryClientForJobservice(repository, internalRegistryURL, secret, internalTokenServiceURL string) (*registry.Repository, error) {
|
func NewRepositoryClientForJobservice(repository, internalRegistryURL, secret, internalTokenServiceURL string) (*registry.Repository, error) {
|
||||||
transport := registry.GetHTTPTransport()
|
transport := registry.GetHTTPTransport()
|
||||||
|
credential := httpauth.NewSecretAuthorizer(secret)
|
||||||
credential := auth.NewCookieCredential(&http.Cookie{
|
|
||||||
Name: models.UISecretCookie,
|
|
||||||
Value: secret,
|
|
||||||
})
|
|
||||||
|
|
||||||
authorizer := auth.NewStandardTokenAuthorizer(&http.Client{
|
authorizer := auth.NewStandardTokenAuthorizer(&http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
@ -70,9 +66,8 @@ func BuildBlobURL(endpoint, repository, digest string) string {
|
|||||||
|
|
||||||
//GetTokenForRepo is used for job handler to get a token for clair.
|
//GetTokenForRepo is used for job handler to get a token for clair.
|
||||||
func GetTokenForRepo(repository, secret, internalTokenServiceURL string) (string, error) {
|
func GetTokenForRepo(repository, secret, internalTokenServiceURL string) (string, error) {
|
||||||
c := &http.Cookie{Name: models.UISecretCookie, Value: secret}
|
credential := httpauth.NewSecretAuthorizer(secret)
|
||||||
credentail := auth.NewCookieCredential(c)
|
t, err := auth.GetToken(internalTokenServiceURL, true, credential,
|
||||||
t, err := auth.GetToken(internalTokenServiceURL, true, credentail,
|
|
||||||
[]*token.ResourceActions{&token.ResourceActions{
|
[]*token.ResourceActions{&token.ResourceActions{
|
||||||
Type: "repository",
|
Type: "repository",
|
||||||
Name: repository,
|
Name: repository,
|
||||||
|
@ -40,7 +40,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
defaultKeyPath string = "/etc/ui/key"
|
defaultKeyPath string = "/etc/ui/key"
|
||||||
defaultTokenFilePath string = "/etc/ui/token/tokens.properties"
|
defaultTokenFilePath string = "/etc/ui/token/tokens.properties"
|
||||||
secretCookieName string = "secret"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -126,7 +126,7 @@ type secretReqCtxModifier struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *secretReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
func (s *secretReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||||
scrt := ctx.GetCookie("secret")
|
scrt := secstore.FromRequest(ctx.Request)
|
||||||
if len(scrt) == 0 {
|
if len(scrt) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
commonsecret "github.com/vmware/harbor/src/common/secret"
|
||||||
"github.com/vmware/harbor/src/common/security"
|
"github.com/vmware/harbor/src/common/security"
|
||||||
"github.com/vmware/harbor/src/common/security/local"
|
"github.com/vmware/harbor/src/common/security/local"
|
||||||
"github.com/vmware/harbor/src/common/security/secret"
|
"github.com/vmware/harbor/src/common/security/secret"
|
||||||
@ -106,11 +107,7 @@ func TestSecretReqCtxModifier(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create request: %v", req)
|
t.Fatalf("failed to create request: %v", req)
|
||||||
}
|
}
|
||||||
req.AddCookie(&http.Cookie{
|
commonsecret.AddToRequest(req, "secret")
|
||||||
Name: "secret",
|
|
||||||
Value: "secret",
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx, err := newContext(req)
|
ctx, err := newContext(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to crate context: %v", err)
|
t.Fatalf("failed to crate context: %v", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user