Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth + config file support #86

Merged
merged 3 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@
# vendor/

mocks/

# Generated during testing
cocli/cocli
cocli/cmd/output.cbor
20 changes: 15 additions & 5 deletions cocli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,10 @@ Tags:
### Submit

Use the `corim submit` subcommand to upload a CoRIM using the Veraison provisioning API.
The CoRIM file containing the CoRIM data in CBOR format is supplied via the
`--corim-file` switch (abbrev. `-f`). The server URL where to upload the CoRIM
The CoRIM file containing the CoRIM data in CBOR format is supplied via the
`--corim-file` switch (abbrev. `-f`). The server URL where to upload the CoRIM
payload is supplied via the `--api-server` switch (abbrev. `-s`).
Further, it is required to supply the media type of the content via the
Further, it is required to supply the media type of the content via the
`--media-type` switch (abbrev. `-m`)
```
$ cocli corim submit \
Expand All @@ -437,6 +437,16 @@ $ cocli corim submit \
>> "corim.cbor" submit ok
```

#### Remote Service Authentication

The above will work if the remote service does not authenticate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question here, more than a comment.

Does the cocli configuration, in any way needs to be synchronised with the
equivalent configuration on the services and apiclient side?
Specifically my question originates from the following line 11 on the config file:
( auth: none)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it would be good to specify what is the default cocli behaviour!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This configuration is used to configure the apiclient so no synchronisation needed, as such, for that. The configuration for cocli does need to match the corresponding configuration for the service it is contacting.

The default is set in the example config, and is specified in the help for the corresponding flag (when you do cocli corim submit -h).

endorsement-provisioning API calls. If the service does authenticate, then
cocli must be configured appropriately. This can be done using a `config.yaml`
file located in the current working directory, or in the standard config
path (usually `~/.config/cocli/config.yaml` on XDG-compliant systems). Please
see `./data/config/example-config.yaml` file for details of the configuration
that needs to be provided.

### Extract CoSWIDs, CoMIDs and CoTSs

Use the `corim extract` subcommand to extract the embedded CoMIDs, CoSWIDs and CoTSs
Expand Down Expand Up @@ -561,7 +571,7 @@ graph LR
cliComidCreate --> CBORComid1
cliCotsCreate --> CBORCots1
cliCoswidCreate --> CBORSwid1

cliCorimCreate --> CBORCorim
cliCorimSign --> CoseSign1
cliCorimVerify --> signBool
Expand All @@ -585,4 +595,4 @@ graph LR
cliCorimExtract --> CBORComid2
cliCorimExtract --> CBORSwid2
cliCorimExtract --> CBORCots2
```
```
40 changes: 34 additions & 6 deletions cocli/cmd/corimSubmit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/veraison/apiclient/provisioning"
)

var (
corimFile *string
apiServer *string
mediaType *string
apiServer string
)

var (
Expand All @@ -33,7 +34,7 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command {
To submit the CBOR-encoded CoRIM from file "unsigned-corim.cbor" with media type
"application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" to the Veraison
provisioning API endpoint "https://veraison.example/endorsement-provisioning/v1", do:


cocli corim submit \
--corim-file=unsigned-corim.cbor \
Expand All @@ -53,28 +54,53 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command {
return fmt.Errorf("read CoRIM payload failed: %w", err)
}

if err = provisionData(data, submitter, *apiServer, *mediaType); err != nil {
if err = provisionData(data, submitter, apiServer, *mediaType); err != nil {
return fmt.Errorf("submit CoRIM payload failed reason: %w", err)
}
return nil
},
}

corimFile = cmd.Flags().StringP("corim-file", "f", "", "name of the CoRIM file in CBOR format")
apiServer = cmd.Flags().StringP("api-server", "s", "", "API server where to submit the corim file")
mediaType = cmd.Flags().StringP("media-type", "m", "", "media type of the CoRIM file")

cmd.Flags().StringP("api-server", "s", "", "API server where to submit the corim file")
cmd.Flags().VarP(&authMethod, "auth", "a",
`authentication method, must be one of "none"/"passthrough", "basic", "oauth2"`)
cmd.Flags().StringP("client-id", "C", "", "OAuth2 client ID")
cmd.Flags().StringP("client-secret", "S", "", "OAuth2 client secret")
cmd.Flags().StringP("token-url", "T", "", "token URL of the OAuth2 service")
cmd.Flags().StringP("username", "U", "", "service username")
cmd.Flags().StringP("password", "P", "", "service password")

err := viper.BindPFlag("api_server", cmd.Flags().Lookup("api-server"))
cobra.CheckErr(err)
err = viper.BindPFlag("auth", cmd.Flags().Lookup("auth"))
cobra.CheckErr(err)
err = viper.BindPFlag("client_id", cmd.Flags().Lookup("client-id"))
cobra.CheckErr(err)
err = viper.BindPFlag("client_secret", cmd.Flags().Lookup("client-secret"))
cobra.CheckErr(err)
err = viper.BindPFlag("username", cmd.Flags().Lookup("username"))
cobra.CheckErr(err)
err = viper.BindPFlag("password", cmd.Flags().Lookup("password"))
cobra.CheckErr(err)
err = viper.BindPFlag("token_url", cmd.Flags().Lookup("token-url"))
cobra.CheckErr(err)

return cmd
}

func checkSubmitArgs() error {
if corimFile == nil || *corimFile == "" {
return errors.New("no CoRIM input file supplied")
}
if apiServer == nil || *apiServer == "" {

apiServer = viper.GetString("api_server")
if apiServer == "" {
return errors.New("no API server supplied")
}
u, err := url.Parse(*apiServer)
u, err := url.Parse(apiServer)
if err != nil || !u.IsAbs() {
return fmt.Errorf("malformed API server URL")
}
Expand All @@ -87,6 +113,8 @@ func checkSubmitArgs() error {
}

func provisionData(data []byte, submitter ISubmitter, uri string, mediaType string) error {
submitter.SetAuth(cliConfig.Auth)

if err := submitter.SetSubmitURI(uri); err != nil {
return fmt.Errorf("unable to set submit URI: %w", err)
}
Expand Down
2 changes: 2 additions & 0 deletions cocli/cmd/corimSubmit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func Test_CorimSubmitCmd_submit_ok(t *testing.T) {
fs = afero.NewMemMapFs()
err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644)
require.NoError(t, err)
ms.EXPECT().SetAuth(gomock.Any())
ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil)
ms.EXPECT().SetDeleteSession(true)
ms.EXPECT().Run(testSignedCorimValid, "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1").Return(nil)
Expand All @@ -155,6 +156,7 @@ func Test_CorimSubmitCmd_submit_not_ok(t *testing.T) {
fs = afero.NewMemMapFs()
err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644)
require.NoError(t, err)
ms.EXPECT().SetAuth(gomock.Any())
ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil)
ms.EXPECT().SetDeleteSession(true)
err = errors.New(`unexpected HTTP response code 404`)
Expand Down
6 changes: 5 additions & 1 deletion cocli/cmd/isubmitter.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package cmd

import "github.com/veraison/apiclient/common"
import (
"github.com/veraison/apiclient/auth"
"github.com/veraison/apiclient/common"
)

type ISubmitter interface {
Run([]byte, string) error
SetClient(client *common.Client) error
SetAuth(a auth.IAuthenticator)
SetSubmitURI(uri string) error
SetDeleteSession(session bool)
}
81 changes: 67 additions & 14 deletions cocli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@
package cmd

import (
"errors"
"fmt"
"os"
"path/filepath"

"github.com/spf13/afero"
"github.com/spf13/cobra"

"github.com/spf13/viper"

"github.com/veraison/apiclient/auth"
)

var (
cfgFile string
fs = afero.NewOsFs()

cliConfig = &ClientConfig{}
authMethod = auth.MethodPassthrough
)

// rootCmd represents the base command when called without any subcommands
Expand All @@ -27,34 +33,81 @@ var rootCmd = &cobra.Command{
SilenceErrors: true,
}

type ClientConfig struct {
Auth auth.IAuthenticator
}

func Execute() {
cobra.CheckErr(rootCmd.Execute())
}

func init() {
cobra.OnInitialize(initConfig)

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)")
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $XDG_CONFIG_HOME/cocli/config.yaml)")
}

// initConfig reads in config file and ENV variables if set
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
v, err := readConfig(cfgFile)
cobra.CheckErr(err)

err = authMethod.Set(v.GetString("auth"))
cobra.CheckErr(err)

switch authMethod {
case auth.MethodPassthrough:
cliConfig.Auth = &auth.NullAuthenticator{}
case auth.MethodBasic:
cliConfig.Auth = &auth.BasicAuthenticator{}
err = cliConfig.Auth.Configure(map[string]interface{}{
"username": v.GetString("username"),
"password": v.GetString("password"),
})
cobra.CheckErr(err)
case auth.MethodOauth2:
cliConfig.Auth = &auth.Oauth2Authenticator{}
err = cliConfig.Auth.Configure(map[string]interface{}{
"client_id": v.GetString("client_id"),
"client_secret": v.GetString("client_secret"),
"token_url": v.GetString("token_url"),
"username": v.GetString("username"),
"password": v.GetString("password"),
})
cobra.CheckErr(err)
default:
// Should never get here as authMethod value is set via
// Method.Set(), which ensures that it's one of the above.
panic(fmt.Sprintf("unknown auth method: %q", authMethod))
}
}

// search config in home directory with name ".cli" (without extension)
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".cli")
func readConfig(path string) (*viper.Viper, error) {
v := viper.GetViper()
if path != "" {
v.SetConfigFile(path)
} else {
wd, err := os.Getwd()
if err != nil {
return nil, err
}

userConfigDir, err := os.UserConfigDir()
if err == nil {
v.AddConfigPath(filepath.Join(userConfigDir, "cocli"))
}
v.AddConfigPath(wd)
v.SetConfigType("yaml")
v.SetConfigName("config")
}

viper.AutomaticEnv() // read in environment variables that match
v.SetEnvPrefix("cocli")
v.AutomaticEnv()

// if a config file is found, read it in
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
err := v.ReadInConfig()
if errors.As(err, &viper.ConfigFileNotFoundError{}) {
err = nil
}

return v, err
}
24 changes: 24 additions & 0 deletions cocli/data/config/example-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This file contains example configuration for connecting to Veraison services.
# This configuration is only necessary for the `cocli corim submit` sub-command,
# as that is the only instance where remote service configuration is used. You
# do not need this configuration for creating or manipulating corims/corim and
# related objects locally.

# API Server submit endpoint URL.
api_server: https://veraison.example/endorsement-provisioning/v1/submit

# Authentication method used by the remote service.
auth: none # may also be "basic" or "oauth2"

# Credentials for the remote service.
username: example_user # used only if auth is "basic" or "oauth2"
password: Passw0rd! # used only if auth is "basic" or "oauth2"; this can also
# be specfied on the command line using --password, or by
# setting COCLI_PASSWORD environment variable

# OAuth2 cofiguration for the authorisation server associated with the remote
# service.
client_id: veraison-client # used only if auth is "oauth2"
client_secret: YifmabB4cVSPPtFLAmHfq7wKaEHQn10Z # used only if auth is "oauth2"
token_url: http://localhost:11111/realms/veraison/protocol/openid-connect/token # used only if auth is "oauth2"

8 changes: 3 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ require (
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/lestrrat-go/jwx/v2 v2.0.8
github.com/spf13/afero v1.6.0
github.com/spf13/afero v1.9.2
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.9.0
github.com/stretchr/testify v1.8.1
github.com/veraison/apiclient v0.0.2
github.com/stretchr/testify v1.8.2
github.com/veraison/apiclient v0.2.0
github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff
github.com/veraison/go-cose v1.0.0-rc.1
github.com/veraison/swid v1.1.0
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
Loading
Loading