Skip to content

Commit

Permalink
Merge pull request #26 from rightscale/flexera_one_auth
Browse files Browse the repository at this point in the history
Support Flexera One authentication
  • Loading branch information
douglaswth authored Oct 21, 2020
2 parents f070cbe + faee5e6 commit 2ffd9d0
Show file tree
Hide file tree
Showing 130 changed files with 49,103 additions and 27,168 deletions.
457 changes: 0 additions & 457 deletions Gopkg.lock

This file was deleted.

63 changes: 0 additions & 63 deletions Gopkg.toml

This file was deleted.

1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ test: lint
sdk:
rm -rf sdk
cp -af $(GOPATH)/src/github.com/rightscale/governance/front_service/gen sdk
rm -rf sdk/*/convert.go sdk/http/*/server
find sdk -type f -name '*.go' -exec sed -i -e 's#github.com/rightscale/governance/front_service/gen#github.com/rightscale/policy_sdk/sdk#' {} \;

# ===== SPECIAL TARGETS FOR fpt =====
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# policy_sdk

This repository contains is a software developer kit for [RightScale Policies](https://docs.rightscale.com/policies/). Policies allow you to automate governance across your multi-cloud environment to increase agility and efficiency while managing security and risk in your organization. Developing [custom policies](https://docs.rightscale.com/policies/getting_started/custom_policy.html) allows the policy engine to interface with almost any publicly available API.
This repository contains is a software developer kit for [Flexera Policies](https://docs.rightscale.com/policies/). Policies allow you to automate governance across your multi-cloud environment to increase agility and efficiency while managing security and risk in your organization. Developing [custom policies](https://docs.rightscale.com/policies/getting_started/custom_policy.html) allows the policy engine to interface with almost any publicly available API.

## fpt

Expand All @@ -13,4 +13,4 @@ The [sdk](https://github.com/rightscale/policy_sdk/tree/master/sdk) directory pr
## License

All source code in this repository is subject to the MIT license, see the
[LICENSE](https://github.com/rightscale/fpt/blob/master/LICENSE) file.
[LICENSE](https://github.com/rightscale/fpt/blob/master/LICENSE) file.
124 changes: 98 additions & 26 deletions auth/refresh_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,115 @@ import (
goahttp "goa.design/goa/v3/http"
)

// refreshTokenSource contains the logic to create new session using OAuth tokens
type refreshTokenSource struct {
endpoint *url.URL
refreshToken string
accessToken string
refreshAt time.Time
doer goahttp.Doer
}
type (
// flexeraRefreshTokenSource contains the logic to create new sessions using OAuth2 tokens
flexeraRefreshTokenSource struct {
endpoint *url.URL
refreshToken string
accessToken string
refreshAt time.Time
doer goahttp.Doer
}

// Grant from CM API 1.5
type Grant struct {
ExpiresIn int `json:"expires_in"`
AccessToken string `json:"access_token"`
}
// rsRefreshTokenSource contains the logic to create new session using OAuth tokens using the legacy RightScale login system
rsRefreshTokenSource struct {
endpoint *url.URL
refreshToken string
accessToken string
refreshAt time.Time
doer goahttp.Doer
}

// Grant represents the grant response returned by the token endpoint
Grant struct {
ExpiresIn int `json:"expires_in"`
AccessToken string `json:"access_token"`
}
)

var (
flexeraHostRegexp = regexp.MustCompile(`^login\.flexera(?:test)?\.(?:com|eu)$`)
cmHostRegexp = regexp.MustCompile(`^(us|telstra|moo)-(\d+)\.(test.)?rightscale\.com$`)
)

// NewOAuthAuthenticator returns a authenticator that uses a oauth refresh
// token to create access tokens. The refresh token can be found in the CM
// dashboard under Settings > Account Settings > API Credentials.
func NewOAuthAuthenticator(host string, refreshToken string) (TokenSource, error) {
if !validHost(host) {
return nil, fmt.Errorf("invalid authentication host, must be a form like us-3.rightscale.com")
if cmHostRegexp.MatchString(host) {
endpoint, _ := url.Parse(fmt.Sprintf("https://%s/api/oauth2", host))
return &rsRefreshTokenSource{
endpoint: endpoint,
refreshToken: refreshToken,
refreshAt: time.Now().Add(-2 * time.Minute),
doer: http.DefaultClient,
}, nil
} else if !flexeraHostRegexp.MatchString(host) {
return nil, fmt.Errorf("invalid authentication host: %v does not match %v or %v", host, flexeraHostRegexp, cmHostRegexp)
}
endpoint, _ := url.Parse(fmt.Sprintf("https://%s/api/oauth2", host))
return &refreshTokenSource{
endpoint, _ := url.Parse(fmt.Sprintf("https://%v/oidc/token", host))
return &flexeraRefreshTokenSource{
endpoint: endpoint,
refreshToken: refreshToken,
refreshAt: time.Now().Add(-2 * time.Minute),
doer: http.DefaultClient,
}, nil
}

func (ts *refreshTokenSource) TokenString() (string, error) {
func (fts *flexeraRefreshTokenSource) TokenString() (string, error) {
if fts.tokenValid() {
return fts.accessToken, nil
}

b, err := json.Marshal(map[string]string{
"grant_type": "refresh_token",
"refresh_token": fts.refreshToken,
})
if err != nil {
return "", err
}
req, err := http.NewRequest("POST", fts.endpoint.String(), bytes.NewBuffer(b))
req.Header.Set("Content-Type", "application/json")

resp, err := fts.doer.Do(req)
if err != nil {
return "", fmt.Errorf("authentication failed: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
b, err := ioutil.ReadAll(resp.Body)
m := "<empty body>"
if err != nil {
m = "<failed to read body>"
} else if len(b) > 0 {
m = string(b)
}
return "", fmt.Errorf("authentication failed: %v: %v", resp.Status, m)
}

b, err = ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("authentication failed: failed to read response: %v", err)
}
var g Grant
err = json.Unmarshal(b, &g)
if err != nil {
return "", fmt.Errorf("authentication failed: failed to parse response: %v", err)
}
if g.AccessToken == "" || g.ExpiresIn == 0 {
return "", fmt.Errorf("authentication failed: grant missing field values: %+v", g)
}

fts.accessToken = g.AccessToken
fts.refreshAt = time.Now().Add(time.Duration(g.ExpiresIn/2) * time.Second)
return fts.accessToken, nil
}

func (fts *flexeraRefreshTokenSource) tokenValid() bool {
return fts.accessToken != "" && !fts.refreshAt.Before(time.Now())
}

func (ts *rsRefreshTokenSource) TokenString() (string, error) {
if ts.tokenValid() {
return ts.accessToken, nil
}
Expand Down Expand Up @@ -91,11 +168,6 @@ func (ts *refreshTokenSource) TokenString() (string, error) {
return ts.accessToken, nil
}

func (ts *refreshTokenSource) tokenValid() bool {
return ts.accessToken != "" && ts.refreshAt.Before(time.Now())
}

func validHost(host string) bool {
hostRe := regexp.MustCompile(`(us|telstra|moo)-(\d+)\.(test.)?rightscale\.com`)
return hostRe.MatchString(host)
func (ts *rsRefreshTokenSource) tokenValid() bool {
return ts.accessToken != "" && !ts.refreshAt.Before(time.Now())
}
5 changes: 5 additions & 0 deletions cmd/fpt/ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
v1.2.0 / 2020-10-21
-------------------
* Add support for Flexera One refresh tokens using an optional `flexera` boolean parameter in account entries in
the configuration file

v1.1.4 / 2020-06-30
-------------------
* Remove double read of access token response for non-200 codes so the actual error message comes through
Expand Down
11 changes: 6 additions & 5 deletions cmd/fpt/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# fpt

`fpt` is a command line tool to aid in the development and testing of [RightScale Policies](https://docs.rightscale.com/policies/). The tool is able to syntax check, upload, and run Policies.
`fpt` is a command line tool to aid in the development and testing of [Flexera Policies](https://docs.rightscale.com/policies/). The tool is able to syntax check, upload, and run Policies.

[![Travis CI Build Status](https://travis-ci.com/rightscale/policy_sdk.svg?branch=master)](https://travis-ci.com/rightscale/policy_sdk)

Expand Down Expand Up @@ -28,9 +28,11 @@ Since `fpt` is written in Go it is compiled to a single static binary. Extract a
1. YAML-based configuration file - Run `fpt config account <name>`, where name is a nickname for the account, to interactively write the configuration file into `$HOME/.fpt.yml` for the first time. You will be prompted for the following fields:
* Account ID - Numeric account number, such as `60073`
* API endpoint host - Hostname, typically `governance-3.rightscale.com`
* Refresh Token - Your personal OAuth token available from **Settings > Account Settings > API Credentials** in the RightScale Cloud Management dashboard
2. Environment variables - These are meant to be used by build systems such as Travis CI. The following vars must be set: `FPT_LOGIN_ACCOUNT_ID`, `FPT_LOGIN_ACCOUNT_HOST`, `FPT_LOGIN_ACCOUNT_REFRESH_TOKEN`. These variables are equivalent to the ones described in the YAML section above.

* Refresh Token - Either:
* Your personal OAuth refresh token available from **Settings > Account Settings > API Credentials** in the RightScale Cloud Management dashboard (this is deprecated in favor of Flexera One and will be removed in a future version)
* Your API refresh token available from [**Flexera One > User Settings > API Credentials**](https://app.flexera.com/settings/api-credentials)
* Flexera One - Whether the Refresh Token is from the Flexera One platform instead of the legacy RightScale dashboard (the value should be `true` for Flexera One or `false` otherwise)
2. Environment variables - These are meant to be used by build systems such as Travis CI. The following vars must be set: `FPT_LOGIN_ACCOUNT_ID`, `FPT_LOGIN_ACCOUNT_HOST`, `FPT_LOGIN_ACCOUNT_REFRESH_TOKEN`. If the refresh token is from the Flexera One platform, you should set `FPT_LOGIN_ACCOUNT_FLEXERA=true` as well. These variables are equivalent to the ones described in the YAML section above.

### Usage

Expand Down Expand Up @@ -89,7 +91,6 @@ This tool is maintained by [Douglas Thrift (douglaswth)](https://github.com/doug
[Peter Schroeter (psschroeter)](https://github.com/psschroeter),
[Avinash Bhashyam (avinashbhashyam-rs)](https://github.com/avinashbhashyam-rs)


## License

The `fpt` source code is subject to the MIT license, see the
Expand Down
2 changes: 1 addition & 1 deletion cmd/fpt/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func printCompileError(err error) {
syntaxErr.Origin, syntaxErr.Problem, syntaxErr.Summary, syntaxErr.Resolution)
}
default:
fmt.Printf("An unexpected error occurred: %v", err)
fmt.Printf("An unexpected error occurred: %v\n", err)
}

}
8 changes: 4 additions & 4 deletions cmd/fpt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

var (
// ----- Top Level -----
app = kingpin.New("fpt", `A command-line application for testing RightScale Policies.
app = kingpin.New("fpt", `A command-line application for testing Flexera Policies.
fpt contains a number of useful commands to help with development of Policies, including a syntax checker and policy runner.
Run fpt --help <command> for additional command specific help.
`)
Expand Down Expand Up @@ -71,9 +71,9 @@ Example: fpt script max_snapshots.pt volumes=@ec2_volumes.json max_count=50 excl
// ----- Configuration -----
configCmd = app.Command("config", "Manage Configuration")

configAccountCmd = configCmd.Command("account", "Add or edit configuration for a RightScale API account")
configAccountName = configAccountCmd.Arg("name", "Name of RightScale API Account to add or edit").Required().String()
configAccountDefault = configAccountCmd.Flag("default", "Set the named RightScale API Account as the default").Short('D').Bool()
configAccountCmd = configCmd.Command("account", "Add or edit configuration for a Flexera One or RightScale API account")
configAccountName = configAccountCmd.Arg("name", "Name of Flexera One or RightScale API Account to add or edit").Required().String()
configAccountDefault = configAccountCmd.Flag("default", "Set the named Flexera One or RightScale API Account as the default").Short('D').Bool()

configShowCmd = configCmd.Command("show", "Show configuration")

Expand Down
Loading

0 comments on commit 2ffd9d0

Please sign in to comment.