Skip to content

Commit

Permalink
Create the token internal command to generate internal JWT for debu…
Browse files Browse the repository at this point in the history
…g purpose (#25)
  • Loading branch information
rockspore authored Jul 30, 2020
1 parent c386d94 commit eecb6ab
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 7 deletions.
62 changes: 58 additions & 4 deletions cmd/token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"time"

"github.com/apigee/apigee-remote-service-cli/shared"
"github.com/apigee/apigee-remote-service-envoy/server"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/jwt"
Expand All @@ -41,10 +43,11 @@ const (

type token struct {
*shared.RootArgs
clientID string
clientSecret string
file string
truncate int
clientID string
clientSecret string
file string
truncate int
internalJWTDuration time.Duration
}

// Cmd returns base command
Expand All @@ -68,6 +71,7 @@ func Cmd(rootArgs *shared.RootArgs, printf shared.FormatFn) *cobra.Command {
c.AddCommand(cmdCreateToken(t, printf))
c.AddCommand(cmdInspectToken(t, printf))
c.AddCommand(cmdRotateCert(t, printf))
c.AddCommand(cmdCreateInternalJWT(t, printf))

return c
}
Expand Down Expand Up @@ -162,6 +166,38 @@ func cmdRotateCert(t *token, printf shared.FormatFn) *cobra.Command {
return c
}

func cmdCreateInternalJWT(t *token, printf shared.FormatFn) *cobra.Command {
c := &cobra.Command{
Use: "internal",
Short: "Create a JWT token for authorizing remote-service API calls (hybrid only)",
Long: "Create a JWT token for authorizing remote-service API calls (hybrid only)",
Args: cobra.NoArgs,

RunE: func(cmd *cobra.Command, _ []string) error {
if !t.IsGCPManaged {
return fmt.Errorf("generating internal JWT only valid for hybrid")
}
if t.internalJWTDuration > 60*time.Minute {
return fmt.Errorf("JWT should not be valid for longer than 1 hour")
}

token, err := t.createInternalJWT(printf)
if err != nil {
return errors.Wrap(err, "creating internal JWT")
}
printf(token)
return nil
},
}

// need to add this config flag here specifically to have it checked first
c.Flags().StringVarP(&t.ConfigPath, "config", "c", "", "Path to Apigee Remote Service config file")
c.Flags().DurationVarP(&t.internalJWTDuration, "duration", "", 10*time.Minute, "Valid time of the internal JWT from creation")
_ = c.MarkFlagRequired("config")

return c
}

func (t *token) createToken(printf shared.FormatFn) (string, error) {
tokenReq := &tokenRequest{
ClientID: t.clientID,
Expand Down Expand Up @@ -191,6 +227,24 @@ func (t *token) createToken(printf shared.FormatFn) (string, error) {
return tokenRes.Token, nil
}

func (t *token) createInternalJWT(printf shared.FormatFn) (string, error) {
if t.ServerConfig == nil {
return "", fmt.Errorf("tenant not found. requires a valid config file")
}
token, err := server.NewToken(t.internalJWTDuration)
if err != nil {
return "", err
}
privateKey := t.ServerConfig.Tenant.PrivateKey
kid := t.ServerConfig.Tenant.PrivateKeyID
signed, err := server.SignJWT(token, jwa.RS256, privateKey, kid)
if err != nil {
return "", err
}
internalJWT := bytes.NewBuffer(signed).String()
return internalJWT, nil
}

func (t *token) inspectToken(in io.Reader, printf shared.FormatFn) error {
var file = in
if t.file != "" {
Expand Down
124 changes: 124 additions & 0 deletions cmd/token/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
package token

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
Expand All @@ -26,14 +30,17 @@ import (
"os"
"strings"
"testing"
"time"

"github.com/apigee/apigee-remote-service-cli/cmd"
"github.com/apigee/apigee-remote-service-cli/shared"
"github.com/apigee/apigee-remote-service-cli/testutil"
"github.com/apigee/apigee-remote-service-envoy/server"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)

func remoteServiceHandler(t *testing.T) http.Handler {
Expand Down Expand Up @@ -410,3 +417,120 @@ func generateJWK(t *testing.T) (*rsa.PrivateKey, jwk.Key) {

return privateKey, key
}

func TestCreateInternalJWT(t *testing.T) {
config := generateConfig(t)

tmpFile, err := ioutil.TempFile("", "config.yaml")
if err != nil {
t.Fatalf("%v", err)
}
if _, err := tmpFile.Write(config); err != nil {
t.Fatalf("%v", err)
}
defer os.Remove(tmpFile.Name())

print := testutil.Printer("TestCreateInternalJWT")

// a good command
rootArgs := &shared.RootArgs{}
flags := []string{"token", "internal", "--config", tmpFile.Name()}
rootCmd := cmd.GetRootCmd(flags, print.Printf)
shared.AddCommandWithFlags(rootCmd, rootArgs, Cmd(rootArgs, print.Printf))

if err := rootCmd.Execute(); err != nil {
t.Errorf("want no error: %v", err)
}

if len(print.Prints) != 1 {
t.Errorf("want 1 output, got %d", len(print.Prints))
}

// missing config
rootArgs = &shared.RootArgs{}
flags = []string{"token", "internal", "-r", "dummy"}
rootCmd = cmd.GetRootCmd(flags, print.Printf)
shared.AddCommandWithFlags(rootCmd, rootArgs, Cmd(rootArgs, print.Printf))

err = rootCmd.Execute()
testutil.ErrorContains(t, err, "required flag(s) \"config\" not set")

// duration too long
rootArgs = &shared.RootArgs{}
flags = []string{"token", "internal", "--config", tmpFile.Name(), "--duration", "80m"}
rootCmd = cmd.GetRootCmd(flags, print.Printf)
shared.AddCommandWithFlags(rootCmd, rootArgs, Cmd(rootArgs, print.Printf))

err = rootCmd.Execute()
testutil.ErrorContains(t, err, "JWT should not be valid for longer than 1 hour")
}

func generateConfig(t *testing.T) []byte {
var yamlBuffer bytes.Buffer
yamlEncoder := yaml.NewEncoder(&yamlBuffer)
yamlEncoder.SetIndent(2)

privateKey, key := generateJWK(t)

config := server.DefaultConfig()
config.Tenant.RemoteServiceAPI = "https://RUNTIME/remote-service"
config.Tenant.OrgName = "hi"
config.Tenant.EnvName = "test"
config.Analytics.FluentdEndpoint = "apigee-udca-hi-test-1q2w3e4r.apigee:20001"
if err := yamlEncoder.Encode(config); err != nil {
t.Fatal(err)
}
configYAML := yamlBuffer.String()
data := map[string]string{"config.yaml": configYAML}

configCRD := server.ConfigMapCRD{
APIVersion: "v1",
Kind: "ConfigMap",
Metadata: server.Metadata{
Name: "apigee-remote-service-envoy",
Namespace: "apigee",
},
Data: data,
}

yamlBuffer.Reset()
yamlEncoder = yaml.NewEncoder(&yamlBuffer)
yamlEncoder.SetIndent(2)
if err := yamlEncoder.Encode(configCRD); err != nil {
t.Fatal(err)
}

privateKeyBytes := pem.EncodeToMemory(&pem.Block{Type: server.PEMKeyType,
Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
jwksBytes, _ := json.Marshal(&jwk.Set{
Keys: []jwk.Key{key},
})
props := map[string]string{server.SecretPropsKIDKey: time.Now().Format(time.RFC3339)}
propsBuf := new(bytes.Buffer)
if err := server.WriteProperties(propsBuf, props); err != nil {
t.Fatal(err)
}

secretData := map[string]string{
server.SecretJKWSKey: base64.StdEncoding.EncodeToString(jwksBytes),
server.SecretPrivateKey: base64.StdEncoding.EncodeToString(privateKeyBytes),
server.SecretPropsKey: base64.StdEncoding.EncodeToString(propsBuf.Bytes()),
}

secretCRD := server.SecretCRD{
APIVersion: "v1",
Kind: "Secret",
Type: "Opaque",
Metadata: server.Metadata{
Name: "hi-test-policy-secret",
Namespace: "apigee",
},
Data: secretData,
}

if err := yamlEncoder.Encode(secretCRD); err != nil {
t.Fatal(err)
}

return yamlBuffer.Bytes()
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ go 1.13
// replace github.com/apigee/apigee-remote-service-envoy => ../apigee-remote-service-envoy

require (
github.com/apigee/apigee-remote-service-envoy v1.0.0-pre.1
github.com/apigee/apigee-remote-service-envoy v1.0.0-pre.1.0.20200729234108-088a461ba862
github.com/apigee/apigee-remote-service-golib v1.0.0-pre.1
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d
github.com/lestrrat-go/jwx v1.0.3
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apigee/apigee-remote-service-envoy v1.0.0-pre.1 h1:Adu3rYTjZhsiABkmJA+bTUCfGCvfMOR+7Mc0ubKY8g8=
github.com/apigee/apigee-remote-service-envoy v1.0.0-pre.1/go.mod h1:WAL8U/tH+W1QaXtWFuQkdHg/G/REDp2Oe71/dc9ai/I=
github.com/apigee/apigee-remote-service-envoy v1.0.0-pre.1.0.20200729234108-088a461ba862 h1:6OWBKSRUfx9keh1GOawWQktqiNlJRw999Jgbz/gDPrM=
github.com/apigee/apigee-remote-service-envoy v1.0.0-pre.1.0.20200729234108-088a461ba862/go.mod h1:WAL8U/tH+W1QaXtWFuQkdHg/G/REDp2Oe71/dc9ai/I=
github.com/apigee/apigee-remote-service-golib v1.0.0-pre.1 h1:VVkN5tIqewCJs7hOdhdyxRBjsXIywMCzs4ChMrbkD8I=
github.com/apigee/apigee-remote-service-golib v1.0.0-pre.1/go.mod h1:C8zgor6NPXg1e8pVdOxAM3N3w4QmxLcY9lnLJl/VOd8=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
Expand Down Expand Up @@ -97,6 +97,7 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
Expand Down Expand Up @@ -207,6 +208,7 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down

0 comments on commit eecb6ab

Please sign in to comment.