Skip to content

Commit

Permalink
cocli: add remote service auth support
Browse files Browse the repository at this point in the history
Add support for remote service authentication to the `corim submit`
sub-command.

Associated with it is support for loading configuration from a file
(note: technically this was already present but was kind of broken and
wasn't actually used for any configuration.)

Signed-off-by: Sergei Trofimov <[email protected]>
  • Loading branch information
setrofim committed Sep 7, 2023
1 parent 2b04ae1 commit 9bc667e
Show file tree
Hide file tree
Showing 8 changed files with 1,164 additions and 37 deletions.
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
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
```
```
26 changes: 25 additions & 1 deletion cocli/cmd/corimSubmit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

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

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 Down Expand Up @@ -64,6 +65,27 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command {
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().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("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
}

Expand All @@ -87,6 +109,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
}
21 changes: 21 additions & 0 deletions cocli/data/config/example-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.

# 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

0 comments on commit 9bc667e

Please sign in to comment.