mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 08:38:03 +01:00
add expiration of robot account
This commit is to make the expiration of robot account configurable 1, The expiration could be set by system admin in the configuation page or by /api/config with robot_token_expiration=60, the default value is 30 days. 2, The expiration could be shown in the robot account infor both on UI and API. Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
4cb49e5388
commit
47a09b5891
@ -4734,6 +4734,9 @@ definitions:
|
||||
description:
|
||||
type: string
|
||||
description: The description of robot account
|
||||
expiration:
|
||||
type: integer
|
||||
description: The expiration of robot account
|
||||
project_id:
|
||||
type: integer
|
||||
description: The project id of robot account
|
||||
|
@ -3,6 +3,7 @@ CREATE TABLE robot (
|
||||
name varchar(255),
|
||||
description varchar(1024),
|
||||
project_id int,
|
||||
expiration int,
|
||||
disabled boolean DEFAULT false NOT NULL,
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
update_time timestamp default CURRENT_TIMESTAMP,
|
||||
|
@ -51,7 +51,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// ConfigList - All configure items used in harbor
|
||||
// Steps to onboard a new setting
|
||||
// 1. Add configure item in metadatalist.go
|
||||
@ -131,5 +130,6 @@ var (
|
||||
{Name: "with_chartmuseum", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CHARTMUSEUM", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
|
||||
{Name: "with_clair", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CLAIR", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
|
||||
{Name: "with_notary", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_NOTARY", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
|
||||
{Name: "robot_token_expiration", Scope: UserScope, Group: BasicGroup, EnvKey: "ROBOT_TOKEN_EXPIRATION", DefaultValue: "30", ItemType: &IntType{}, Editable: true},
|
||||
}
|
||||
)
|
||||
|
@ -117,6 +117,7 @@ const (
|
||||
DefaultRegistryCtlURL = "http://registryctl:8080"
|
||||
DefaultClairHealthCheckServerURL = "http://clair:6061"
|
||||
// Use this prefix to distinguish harbor user, the prefix contains a special character($), so it cannot be registered as a harbor user.
|
||||
RobotPrefix = "robot$"
|
||||
CoreConfigPath = "/api/internal/configurations"
|
||||
RobotPrefix = "robot$"
|
||||
CoreConfigPath = "/api/internal/configurations"
|
||||
RobotTokenExpiration = "robot_token_expiration"
|
||||
)
|
||||
|
@ -29,6 +29,7 @@ type Robot struct {
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Description string `orm:"column(description)" json:"description"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
Expiration int64 `orm:"column(expiration)" json:"expiration"`
|
||||
Disabled bool `orm:"column(disabled)" json:"disabled"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
|
@ -25,5 +25,9 @@ func (rc RobotClaims) Valid() error {
|
||||
if rc.Access == nil {
|
||||
return errors.New("The access info cannot be nil")
|
||||
}
|
||||
stdErr := rc.StandardClaims.Valid()
|
||||
if stdErr != nil {
|
||||
return stdErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -19,14 +19,15 @@ type HToken struct {
|
||||
}
|
||||
|
||||
// New ...
|
||||
func New(tokenID, projectID int64, access []*rbac.Policy) (*HToken, error) {
|
||||
func New(tokenID, projectID, expiresAt int64, access []*rbac.Policy) (*HToken, error) {
|
||||
rClaims := &RobotClaims{
|
||||
TokenID: tokenID,
|
||||
ProjectID: projectID,
|
||||
Access: access,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(DefaultOptions.TTL).Unix(),
|
||||
Issuer: DefaultOptions.Issuer,
|
||||
IssuedAt: time.Now().UTC().Unix(),
|
||||
ExpiresAt: expiresAt,
|
||||
Issuer: DefaultOptions().Issuer,
|
||||
},
|
||||
}
|
||||
err := rClaims.Valid()
|
||||
@ -34,13 +35,13 @@ func New(tokenID, projectID int64, access []*rbac.Policy) (*HToken, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &HToken{
|
||||
Token: *jwt.NewWithClaims(DefaultOptions.SignMethod, rClaims),
|
||||
Token: *jwt.NewWithClaims(DefaultOptions().SignMethod, rClaims),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Raw get the Raw string of token
|
||||
func (htk *HToken) Raw() (string, error) {
|
||||
key, err := DefaultOptions.GetKey()
|
||||
key, err := DefaultOptions().GetKey()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
@ -54,12 +55,12 @@ func (htk *HToken) Raw() (string, error) {
|
||||
|
||||
// ParseWithClaims ...
|
||||
func ParseWithClaims(rawToken string, claims jwt.Claims) (*HToken, error) {
|
||||
key, err := DefaultOptions.GetKey()
|
||||
key, err := DefaultOptions().GetKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := jwt.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
if token.Method.Alg() != DefaultOptions.SignMethod.Alg() {
|
||||
if token.Method.Alg() != DefaultOptions().SignMethod.Alg() {
|
||||
return nil, errors.New("invalid signing method")
|
||||
}
|
||||
switch k := key.(type) {
|
||||
@ -75,9 +76,10 @@ func ParseWithClaims(rawToken string, claims jwt.Claims) (*HToken, error) {
|
||||
log.Errorf(fmt.Sprintf("parse token error, %v", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
log.Errorf(fmt.Sprintf("invalid jwt token, %v", token))
|
||||
return nil, err
|
||||
return nil, errors.New("invalid jwt token")
|
||||
}
|
||||
return &HToken{
|
||||
Token: *token,
|
||||
|
@ -3,6 +3,7 @@ package token
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
@ -30,7 +31,9 @@ func TestNew(t *testing.T) {
|
||||
|
||||
tokenID := int64(123)
|
||||
projectID := int64(321)
|
||||
token, err := New(tokenID, projectID, policies)
|
||||
tokenExpiration := time.Duration(10) * 24 * time.Hour
|
||||
expiresAt := time.Now().UTC().Add(tokenExpiration).Unix()
|
||||
token, err := New(tokenID, projectID, expiresAt, policies)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, token.Header["alg"], "RS256")
|
||||
@ -49,7 +52,9 @@ func TestRaw(t *testing.T) {
|
||||
tokenID := int64(123)
|
||||
projectID := int64(321)
|
||||
|
||||
token, err := New(tokenID, projectID, policies)
|
||||
tokenExpiration := time.Duration(10) * 24 * time.Hour
|
||||
expiresAt := time.Now().UTC().Add(tokenExpiration).Unix()
|
||||
token, err := New(tokenID, projectID, expiresAt, policies)
|
||||
assert.Nil(t, err)
|
||||
|
||||
rawTk, err := token.Raw()
|
||||
|
@ -16,12 +16,6 @@ const (
|
||||
signedMethod = "RS256"
|
||||
)
|
||||
|
||||
var (
|
||||
privateKey = config.TokenPrivateKeyPath()
|
||||
// DefaultOptions ...
|
||||
DefaultOptions = NewOptions()
|
||||
)
|
||||
|
||||
// Options ...
|
||||
type Options struct {
|
||||
SignMethod jwt.SigningMethod
|
||||
@ -31,9 +25,10 @@ type Options struct {
|
||||
Issuer string
|
||||
}
|
||||
|
||||
// NewOptions ...
|
||||
func NewOptions() *Options {
|
||||
privateKey, err := ioutil.ReadFile(privateKey)
|
||||
// DefaultOptions ...
|
||||
func DefaultOptions() *Options {
|
||||
privateKeyFile := config.TokenPrivateKeyPath()
|
||||
privateKey, err := ioutil.ReadFile(privateKeyFile)
|
||||
if err != nil {
|
||||
log.Errorf(fmt.Sprintf("failed to read private key %v", err))
|
||||
return nil
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewOptions(t *testing.T) {
|
||||
defaultOpt := DefaultOptions
|
||||
defaultOpt := DefaultOptions()
|
||||
assert.NotNil(t, defaultOpt)
|
||||
assert.Equal(t, defaultOpt.SignMethod, jwt.GetSigningMethod("RS256"))
|
||||
assert.Equal(t, defaultOpt.Issuer, "harbor-token-issuer")
|
||||
@ -16,7 +16,7 @@ func TestNewOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetKey(t *testing.T) {
|
||||
defaultOpt := DefaultOptions
|
||||
defaultOpt := DefaultOptions()
|
||||
key, err := defaultOpt.GetKey()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, key)
|
||||
|
@ -16,14 +16,16 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/token"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RobotAPI ...
|
||||
@ -104,6 +106,9 @@ func (r *RobotAPI) Post() {
|
||||
}
|
||||
|
||||
var robotReq models.RobotReq
|
||||
// Expiration in days
|
||||
tokenExpiration := time.Duration(config.RobotTokenExpiration()) * 24 * time.Hour
|
||||
expiresAt := time.Now().UTC().Add(tokenExpiration).Unix()
|
||||
r.DecodeJSONReq(&robotReq)
|
||||
createdName := common.RobotPrefix + robotReq.Name
|
||||
|
||||
@ -112,6 +117,7 @@ func (r *RobotAPI) Post() {
|
||||
Name: createdName,
|
||||
Description: robotReq.Description,
|
||||
ProjectID: r.project.ProjectID,
|
||||
Expiration: expiresAt,
|
||||
}
|
||||
id, err := dao.AddRobot(&robot)
|
||||
if err != nil {
|
||||
@ -125,7 +131,7 @@ func (r *RobotAPI) Post() {
|
||||
|
||||
// generate the token, and return it with response data.
|
||||
// token is not stored in the database.
|
||||
jwtToken, err := token.New(id, r.project.ProjectID, robotReq.Access)
|
||||
jwtToken, err := token.New(id, r.project.ProjectID, expiresAt, robotReq.Access)
|
||||
if err != nil {
|
||||
r.HandleInternalServerError(fmt.Sprintf("failed to valid parameters to generate token for robot account, %v", err))
|
||||
err := dao.DeleteRobot(id)
|
||||
|
@ -225,6 +225,11 @@ func TokenExpiration() (int, error) {
|
||||
return cfgMgr.Get(common.TokenExpiration).GetInt(), nil
|
||||
}
|
||||
|
||||
// RobotTokenExpiration returns the token expiration time of robot account (in day)
|
||||
func RobotTokenExpiration() int {
|
||||
return cfgMgr.Get(common.RobotTokenExpiration).GetInt()
|
||||
}
|
||||
|
||||
// ExtEndpoint returns the external URL of Harbor: protocol://host:port
|
||||
func ExtEndpoint() (string, error) {
|
||||
return cfgMgr.Get(common.ExtEndpoint).GetString(), nil
|
||||
|
@ -97,6 +97,9 @@ func TestConfig(t *testing.T) {
|
||||
t.Fatalf("failed to get token expiration: %v", err)
|
||||
}
|
||||
|
||||
tkExp := RobotTokenExpiration()
|
||||
assert.Equal(tkExp, 30)
|
||||
|
||||
if _, err := ExtEndpoint(); err != nil {
|
||||
t.Fatalf("failed to get domain name: %v", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user