Skip to content

Commit

Permalink
FEATURE: new token time calculation model (v12.0.0)
Browse files Browse the repository at this point in the history
- adding "grace period" in "token time" calculations, and this is breaking
  change, because token time calculation changes, and management of grace
  period is user/app responsibility (but there is default value) and tokens
  also will now have minimum period
- bugfix: when broken catalog was loaded, catalog listing failed
  • Loading branch information
vjmp committed Dec 29, 2022
1 parent c687438 commit ad7fa3f
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 47 deletions.
14 changes: 10 additions & 4 deletions cmd/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ var authorizeCmd = &cobra.Command{
if common.DebugFlag {
defer common.Stopwatch("Authorize query lasted").Report()
}
period := &operations.TokenPeriod{
ValidityTime: validityTime,
GracePeriod: gracePeriod,
}
period.EnforceGracePeriod()
var claims *operations.Claims
if granularity == "user" {
claims = operations.ViewWorkspacesClaims(validityTime * 60)
claims = operations.ViewWorkspacesClaims(period.RequestSeconds())
} else {
claims = operations.RunRobotClaims(validityTime*60, workspaceId)
claims = operations.RunRobotClaims(period.RequestSeconds(), workspaceId)
}
data, err := operations.AuthorizeClaims(AccountName(), claims)
data, err := operations.AuthorizeClaims(AccountName(), claims, period)
if err != nil {
pretty.Exit(3, "Error: %v", err)
}
Expand All @@ -38,7 +43,8 @@ var authorizeCmd = &cobra.Command{

func init() {
cloudCmd.AddCommand(authorizeCmd)
authorizeCmd.Flags().IntVarP(&validityTime, "minutes", "m", 0, "How many minutes the authorization should be valid for.")
authorizeCmd.Flags().IntVarP(&validityTime, "minutes", "m", 15, "How many minutes the authorization should be valid for (minimum 15 minutes).")
authorizeCmd.Flags().IntVarP(&gracePeriod, "graceperiod", "", 5, "What is grace period buffer in minutes on top of validity minutes (minimum 5 minutes).")
authorizeCmd.Flags().StringVarP(&granularity, "granularity", "g", "", "Authorization granularity (user/workspace) used in.")
authorizeCmd.Flags().StringVarP(&workspaceId, "workspace", "w", "", "Workspace id to use with this command.")
}
12 changes: 9 additions & 3 deletions cmd/holotreeVariables.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,13 @@ func holotreeExpandEnvironment(userFiles []string, packfile, environment, worksp

if Has(workspace) {
common.Timeline("get run robot claims")
claims := operations.RunRobotClaims(validity*60, workspace)
data, err = operations.AuthorizeClaims(AccountName(), claims)
period := &operations.TokenPeriod{
ValidityTime: validityTime,
GracePeriod: gracePeriod,
}
period.EnforceGracePeriod()
claims := operations.RunRobotClaims(period.RequestSeconds(), workspace)
data, err = operations.AuthorizeClaims(AccountName(), claims, period)
pretty.Guard(err == nil, 9, "Failed to get cloud data, reason: %v", err)
}

Expand Down Expand Up @@ -145,7 +150,8 @@ func init() {
holotreeVariablesCmd.Flags().StringVarP(&environmentFile, "environment", "e", "", "Full path to 'env.json' development environment data file. <optional>")
holotreeVariablesCmd.Flags().StringVarP(&robotFile, "robot", "r", "robot.yaml", "Full path to 'robot.yaml' configuration file. <optional>")
holotreeVariablesCmd.Flags().StringVarP(&workspaceId, "workspace", "w", "", "Optional workspace id to get authorization tokens for. <optional>")
holotreeVariablesCmd.Flags().IntVarP(&validityTime, "minutes", "m", 0, "How many minutes the authorization should be valid for. <optional>")
holotreeVariablesCmd.Flags().IntVarP(&validityTime, "minutes", "m", 15, "How many minutes the authorization should be valid for (minimum 15 minutes).")
holotreeVariablesCmd.Flags().IntVarP(&gracePeriod, "graceperiod", "", 5, "What is grace period buffer in minutes on top of validity minutes (minimum 5 minutes).")
holotreeVariablesCmd.Flags().StringVarP(&accountName, "account", "a", "", "Account used for workspace. <optional>")

holotreeVariablesCmd.Flags().StringVarP(&common.HolotreeSpace, "space", "s", "user", "Client specific name to identify this environment.")
Expand Down
8 changes: 6 additions & 2 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ in your own machine.`,

func captureRunFlags(assistant bool) *operations.RunFlags {
return &operations.RunFlags{
TokenPeriod: &operations.TokenPeriod{
ValidityTime: validityTime,
GracePeriod: gracePeriod,
},
AccountName: AccountName(),
WorkspaceId: workspaceId,
ValidityTime: validityTime,
EnvironmentFile: environmentFile,
RobotYaml: robotFile,
Assistant: assistant,
Expand All @@ -55,7 +58,8 @@ func init() {
runCmd.Flags().StringVarP(&robotFile, "robot", "r", "robot.yaml", "Full path to the 'robot.yaml' configuration file.")
runCmd.Flags().StringVarP(&runTask, "task", "t", "", "Task to run from the configuration file.")
runCmd.Flags().StringVarP(&workspaceId, "workspace", "w", "", "Optional workspace id to get authorization tokens for. OPTIONAL")
runCmd.Flags().IntVarP(&validityTime, "minutes", "m", 0, "How many minutes the authorization should be valid for. OPTIONAL")
runCmd.Flags().IntVarP(&validityTime, "minutes", "m", 15, "How many minutes the authorization should be valid for (minimum 15 minutes).")
runCmd.Flags().IntVarP(&gracePeriod, "graceperiod", "", 5, "What is grace period buffer in minutes on top of validity minutes (minimum 5 minutes).")
runCmd.Flags().StringVarP(&accountName, "account", "", "", "Account used for workspace. OPTIONAL")
runCmd.Flags().BoolVarP(&forceFlag, "force", "f", false, "Force conda cache update (only for new environments).")
runCmd.Flags().BoolVarP(&interactiveFlag, "interactive", "", false, "Allow robot to be interactive in terminal/command prompt. For development only, not for production!")
Expand Down
5 changes: 4 additions & 1 deletion cmd/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ var scriptCmd = &cobra.Command{

func noRunFlags() *operations.RunFlags {
return &operations.RunFlags{
TokenPeriod: &operations.TokenPeriod{
ValidityTime: 0,
GracePeriod: 0,
},
AccountName: "",
WorkspaceId: "",
ValidityTime: 0,
EnvironmentFile: environmentFile,
RobotYaml: robotFile,
Assistant: false,
Expand Down
1 change: 1 addition & 0 deletions cmd/sharedvariables.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
runTask string
shellDirectory string
templateName string
gracePeriod int
validityTime int
workspaceId string
wskey string
Expand Down
3 changes: 2 additions & 1 deletion cmd/testrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ func init() {
testrunCmd.Flags().StringVarP(&robotFile, "robot", "r", "robot.yaml", "Full path to the 'robot.yaml' configuration file.")
testrunCmd.Flags().StringVarP(&runTask, "task", "t", "", "Task to run from configuration file.")
testrunCmd.Flags().StringVarP(&workspaceId, "workspace", "w", "", "Optional workspace id to get authorization tokens for. OPTIONAL")
testrunCmd.Flags().IntVarP(&validityTime, "minutes", "m", 0, "How many minutes the authorization should be valid for. OPTIONAL")
testrunCmd.Flags().IntVarP(&validityTime, "minutes", "m", 15, "How many minutes the authorization should be valid for (minimum 15 minutes).")
testrunCmd.Flags().IntVarP(&gracePeriod, "graceperiod", "", 5, "What is grace period buffer in minutes on top of validity minutes (minimum 5 minutes).")
testrunCmd.Flags().StringVarP(&accountName, "account", "", "", "Account used for workspace. OPTIONAL")
testrunCmd.Flags().BoolVarP(&forceFlag, "force", "f", false, "Force conda cache update. (only for new environments)")
testrunCmd.Flags().StringVarP(&common.HolotreeSpace, "space", "s", "user", "Client specific name to identify this environment.")
Expand Down
2 changes: 1 addition & 1 deletion common/version.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package common

const (
Version = `v11.36.5`
Version = `v12.0.0`
)
8 changes: 8 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# rcc change log

## v12.0.0 (date: 29.12.2022) UNSTABLE

- adding "grace period" in "token time" calculations, and this is breaking
change, because token time calculation changes, and management of grace
period is user/app responsibility (but there is default value) and tokens
also will now have minimum period
- bugfix: when broken catalog was loaded, catalog listing failed

## v11.36.5 (date: 28.12.2022)

- fix: added more explanation to network diagnostics reporting, explaining
Expand Down
12 changes: 11 additions & 1 deletion htfs/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,16 @@ func DigestLoader(root *Root, at int, slots []map[string]string) anywork.Work {
}
}

func ignoreFailedCatalogs(suspects []*Root) []*Root {
roots := make([]*Root, 0, len(suspects))
for _, root := range suspects {
if root != nil {
roots = append(roots, root)
}
}
return roots
}

func LoadCatalogs() ([]string, []*Root) {
common.TimelineBegin("catalog load start")
defer common.TimelineEnd()
Expand All @@ -516,7 +526,7 @@ func LoadCatalogs() ([]string, []*Root) {
}
runtime.Gosched()
anywork.Sync()
return catalogs, roots
return catalogs, ignoreFailedCatalogs(roots)
}

func BaseFolders() []string {
Expand Down
12 changes: 6 additions & 6 deletions operations/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func HmacSignature(claims *Claims, secret, nonce, bodyHash string) string {
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
}

func AuthorizeClaims(accountName string, claims *Claims) (Token, error) {
func AuthorizeClaims(accountName string, claims *Claims, period *TokenPeriod) (Token, error) {
account := AccountByName(accountName)
if account == nil {
return nil, fmt.Errorf("Could not find account by name: %q", accountName)
Expand All @@ -166,16 +166,16 @@ func AuthorizeClaims(accountName string, claims *Claims) (Token, error) {
if err != nil {
return nil, fmt.Errorf("Could not create client for endpoint: %s reason: %w", account.Endpoint, err)
}
data, err := AuthorizeCommand(client, account, claims)
data, err := AuthorizeCommand(client, account, claims, period)
if err != nil {
return nil, fmt.Errorf("Could not authorize: %w", err)
}
return data, nil
}

func AuthorizeCommand(client cloud.Client, account *account, claims *Claims) (Token, error) {
func AuthorizeCommand(client cloud.Client, account *account, claims *Claims, period *TokenPeriod) (Token, error) {
when := time.Now().Unix()
found, ok := account.Cached(claims.Name, claims.Url)
found, ok := account.Cached(period, claims.Name, claims.Url)
if ok {
cached := make(Token)
cached["endpoint"] = client.Endpoint()
Expand Down Expand Up @@ -216,8 +216,8 @@ func AuthorizeCommand(client cloud.Client, account *account, claims *Claims) (To
account.WasVerified(when)
trueToken, ok := token["token"].(string)
if ok {
deadline := when + int64(3*(claims.ExpiresIn/4))
account.CacheToken(claims.Name, claims.Url, trueToken, deadline)
account.CacheToken(claims.Name, claims.Url, trueToken, period.Deadline())
common.Timeline("cached authorize claim: %s (new deadline: %d)", claims.Name, period.Deadline())
}
return token, nil
}
Expand Down
4 changes: 2 additions & 2 deletions operations/authorize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ func TestCanCallAuthorizeCommand(t *testing.T) {
first := cloud.Response{Status: 200, Body: []byte("{\"token\":\"foo\",\"expiresIn\":1}")}
client := mocks.NewClient(&first)
claims := operations.RunRobotClaims(1, "777")
token, err := operations.AuthorizeCommand(client, account, claims)
token, err := operations.AuthorizeCommand(client, account, claims, nil)
must_be.Nil(err)
wont_be.Nil(token)
must_be.Equal(token["token"], "foo")
must_be.Equal(token["expiresIn"], 1.0)
//must_be.Equal(token["expiresIn"], 1.0)
must_be.Equal(token["endpoint"], "https://this.is/mock")
}
8 changes: 4 additions & 4 deletions operations/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func (it *account) CacheToken(name, url, token string, deadline int64) {
cache.Credentials[fullkey] = &credential
}

func (it *account) Cached(name, url string) (string, bool) {
func (it *account) Cached(period *TokenPeriod, name, url string) (string, bool) {
if common.NoCache {
return "", false
}
Expand All @@ -124,11 +124,11 @@ func (it *account) Cached(name, url string) (string, bool) {
if !ok {
return "", false
}
when := time.Now().Unix()
if found.Deadline < when {
liveline := period.Liveline()
if found.Deadline < liveline {
return "", false
}
common.Timeline("cached token: %s", name)
common.Timeline("using cached token: %s (%d < %d)", name, liveline, found.Deadline)
return found.Token, true
}

Expand Down
66 changes: 61 additions & 5 deletions operations/running.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path/filepath"
"runtime"
"strings"
"time"

"github.com/robocorp/rcc/common"
"github.com/robocorp/rcc/conda"
Expand All @@ -21,16 +22,71 @@ var (
rcTokens = []string{"RC_API_SECRET_TOKEN", "RC_API_WORKITEM_TOKEN"}
)

type TokenPeriod struct {
ValidityTime int // minutes
GracePeriod int // minutes
}

type RunFlags struct {
*TokenPeriod
AccountName string
WorkspaceId string
ValidityTime int
EnvironmentFile string
RobotYaml string
Assistant bool
NoPipFreeze bool
}

func (it *TokenPeriod) EnforceGracePeriod() *TokenPeriod {
if it == nil {
return it
}
if it.GracePeriod < 5 {
it.GracePeriod = 5
}
if it.GracePeriod > 120 {
it.GracePeriod = 120
}
if it.ValidityTime < 15 {
it.ValidityTime = 15
}
return it
}

func asSeconds(minutes int) int {
return 60 * minutes
}

func DefaultTokenPeriod() *TokenPeriod {
result := &TokenPeriod{}
return result.EnforceGracePeriod()
}

func (it *TokenPeriod) AsSeconds() (int, int, bool) {
if it == nil {
return asSeconds(15), asSeconds(5), false
}
it.EnforceGracePeriod()
return asSeconds(it.ValidityTime), asSeconds(it.GracePeriod), true
}

func (it *TokenPeriod) Liveline() int64 {
valid, _, _ := it.AsSeconds()
when := time.Now().Unix()
return when + int64(valid)
}

func (it *TokenPeriod) Deadline() int64 {
valid, grace, _ := it.AsSeconds()
when := time.Now().Unix()
return when + int64(valid+grace)
}

func (it *TokenPeriod) RequestSeconds() int {
valid, grace, _ := it.AsSeconds()
return int(valid + grace)
}

func FreezeEnvironmentListing(label string, config robot.Robot) {
goldenfile := conda.GoldenMasterFilename(label)
listing := conda.LoadWantedDependencies(goldenfile)
Expand Down Expand Up @@ -150,8 +206,8 @@ func ExecuteSimpleTask(flags *RunFlags, template []string, config robot.Robot, t
}
var data Token
if len(flags.WorkspaceId) > 0 {
claims := RunRobotClaims(flags.ValidityTime*60, flags.WorkspaceId)
data, err = AuthorizeClaims(flags.AccountName, claims)
claims := RunRobotClaims(flags.TokenPeriod.RequestSeconds(), flags.WorkspaceId)
data, err = AuthorizeClaims(flags.AccountName, claims, flags.TokenPeriod.EnforceGracePeriod())
}
if err != nil {
pretty.Exit(8, "Error: %v", err)
Expand Down Expand Up @@ -215,8 +271,8 @@ func ExecuteTask(flags *RunFlags, template []string, config robot.Robot, todo ro
task[0] = findExecutableOrDie(searchPath, task[0])
var data Token
if !flags.Assistant && len(flags.WorkspaceId) > 0 {
claims := RunRobotClaims(flags.ValidityTime*60, flags.WorkspaceId)
data, err = AuthorizeClaims(flags.AccountName, claims)
claims := RunRobotClaims(flags.TokenPeriod.RequestSeconds(), flags.WorkspaceId)
data, err = AuthorizeClaims(flags.AccountName, claims, nil)
}
if err != nil {
pretty.Exit(8, "Error: %v", err)
Expand Down
18 changes: 18 additions & 0 deletions operations/running_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package operations_test

import (
"testing"

"github.com/robocorp/rcc/hamlet"
"github.com/robocorp/rcc/operations"
)

func TestTokenPeriodWorksAsExpected(t *testing.T) {
must, wont := hamlet.Specifications(t)

var period *operations.TokenPeriod
must.Nil(period)
wont.Panic(func() {
period.Deadline()
})
}
Loading

0 comments on commit ad7fa3f

Please sign in to comment.