Skip to content

Commit 49cd3ea

Browse files
authored
TLS Protect Cloud Authentication Addition (#445)
* Add TLS Protect Cloud authentication to the agent * Change config flag, set default url in venafi cloud mode, and consolidate the way credentials are processed * Return correct credential type and use json vs yaml for cred config * Changing from uploadID to uploaderID
1 parent 0d02f56 commit 49cd3ea

File tree

10 files changed

+535
-29
lines changed

10 files changed

+535
-29
lines changed

agent.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ data-gatherers:
1111
name: "dummy-fail"
1212
config:
1313
always-fail: true
14+
venafi-cloud:
15+
upload_id: "example-id"
16+
upload_path: "/example/endpoint/path"
17+

cmd/agent.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ func init() {
7676
"",
7777
"Location of the credentials file. For OAuth2 based authentication.",
7878
)
79+
agentCmd.PersistentFlags().BoolVarP(
80+
&agent.VenafiCloudMode,
81+
"venafi-cloud",
82+
"",
83+
false,
84+
"Runs agent with parsing config and credentials file in Venafi Cloud format if true.",
85+
)
7986
agentCmd.PersistentFlags().BoolVarP(
8087
&agent.OneShot,
8188
"one-shot",

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/cenkalti/backoff v2.2.1+incompatible
88
github.com/d4l3k/messagediff v1.2.1
99
github.com/fatih/color v1.15.0
10+
github.com/google/uuid v1.3.0
1011
github.com/hashicorp/go-multierror v1.1.1
1112
github.com/json-iterator/go v1.1.12
1213
github.com/juju/errors v1.0.0
@@ -36,11 +37,11 @@ require (
3637
github.com/go-openapi/jsonreference v0.20.0 // indirect
3738
github.com/go-openapi/swag v0.21.1 // indirect
3839
github.com/gogo/protobuf v1.3.2 // indirect
40+
github.com/golang-jwt/jwt/v4 v4.5.0
3941
github.com/golang/protobuf v1.5.3 // indirect
4042
github.com/google/gnostic v0.6.9 // indirect
4143
github.com/google/go-cmp v0.5.9 // indirect
4244
github.com/google/gofuzz v1.2.0 // indirect
43-
github.com/google/uuid v1.3.0 // indirect
4445
github.com/hashicorp/errwrap v1.1.0 // indirect
4546
github.com/imdario/mergo v0.3.12 // indirect
4647
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -65,7 +66,7 @@ require (
6566
google.golang.org/appengine v1.6.7 // indirect
6667
google.golang.org/protobuf v1.30.0 // indirect
6768
gopkg.in/inf.v0 v0.9.1 // indirect
68-
gopkg.in/yaml.v3 v3.0.1 // indirect
69+
gopkg.in/yaml.v3 v3.0.1
6970
k8s.io/klog/v2 v2.80.1 // indirect
7071
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
7172
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrK
5353
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
5454
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
5555
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
56+
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
57+
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
5658
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
5759
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
5860
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=

pkg/agent/config.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/jetstack/preflight/pkg/datagatherer/k8s"
1111
"github.com/jetstack/preflight/pkg/datagatherer/local"
1212
"github.com/pkg/errors"
13-
"gopkg.in/yaml.v2"
13+
"gopkg.in/yaml.v3"
1414
)
1515

1616
// Config wraps the options for a run of the agent.
@@ -30,7 +30,8 @@ type Config struct {
3030
// InputPath replaces DataGatherers with input data file
3131
InputPath string `yaml:"input-path"`
3232
// OutputPath replaces Server with output data file
33-
OutputPath string `yaml:"output-path"`
33+
OutputPath string `yaml:"output-path"`
34+
VenafiCloud *VenafiCloudConfig `yaml:"venafi-cloud,omitempty"`
3435
}
3536

3637
type Endpoint struct {
@@ -46,6 +47,14 @@ type DataGatherer struct {
4647
Config datagatherer.Config
4748
}
4849

50+
type VenafiCloudConfig struct {
51+
// UploaderID is the upload ID that will be used when
52+
// creating a cluster connection
53+
UploaderID string `yaml:"uploader_id,omitempty"`
54+
// UploadPath is the endpoint path for the upload API.
55+
UploadPath string `yaml:"upload_path,omitempty"`
56+
}
57+
4958
func reMarshal(rawConfig interface{}, config datagatherer.Config) error {
5059
bb, err := yaml.Marshal(rawConfig)
5160
if err != nil {
@@ -120,11 +129,25 @@ func (c *Config) Dump() (string, error) {
120129
func (c *Config) validate() error {
121130
var result *multierror.Error
122131

123-
if c.OrganizationID == "" {
124-
result = multierror.Append(result, fmt.Errorf("organization_id is required"))
125-
}
126-
if c.ClusterID == "" {
127-
result = multierror.Append(result, fmt.Errorf("cluster_id is required"))
132+
// configured for Venafi Cloud
133+
if c.VenafiCloud != nil {
134+
if c.VenafiCloud.UploaderID == "" {
135+
result = multierror.Append(result, fmt.Errorf("upload_id is required in Venafi Cloud mode"))
136+
}
137+
if c.VenafiCloud.UploadPath == "" {
138+
result = multierror.Append(result, fmt.Errorf("upload_path is required in Venafi Cloud mode"))
139+
}
140+
141+
if _, err := url.Parse(c.VenafiCloud.UploadPath); err != nil {
142+
result = multierror.Append(result, fmt.Errorf("upload_path is not a valid URL"))
143+
}
144+
} else {
145+
if c.OrganizationID == "" {
146+
result = multierror.Append(result, fmt.Errorf("organization_id is required"))
147+
}
148+
if c.ClusterID == "" {
149+
result = multierror.Append(result, fmt.Errorf("cluster_id is required"))
150+
}
128151
}
129152

130153
if c.Server != "" {
@@ -155,7 +178,11 @@ func ParseConfig(data []byte) (Config, error) {
155178
}
156179

157180
if config.Server == "" && config.Endpoint.Host == "" && config.Endpoint.Path == "" {
158-
config.Server = "https://preflight.jetstack.io"
181+
if config.VenafiCloud != nil {
182+
config.Server = "https://api.venafi.cloud"
183+
} else {
184+
config.Server = "https://preflight.jetstack.io"
185+
}
159186
}
160187

161188
if config.Endpoint.Protocol == "" && config.Server == "" {

pkg/agent/config_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,54 @@ func TestValidConfigWithEndpointLoad(t *testing.T) {
9898
}
9999
}
100100

101+
func TestValidVenafiCloudConfigLoad(t *testing.T) {
102+
configFileContents := `
103+
server: "http://localhost:8080"
104+
period: 1h
105+
data-gatherers:
106+
- name: d1
107+
kind: dummy
108+
config:
109+
always-fail: false
110+
input-path: "/home"
111+
output-path: "/nothome"
112+
venafi-cloud:
113+
uploader_id: test-agent
114+
upload_path: "/testing/path"
115+
`
116+
117+
loadedConfig, err := ParseConfig([]byte(configFileContents))
118+
if err != nil {
119+
t.Fatalf("unexpected error: %v", err)
120+
}
121+
122+
expected := Config{
123+
Server: "http://localhost:8080",
124+
Period: time.Hour,
125+
OrganizationID: "",
126+
ClusterID: "",
127+
DataGatherers: []DataGatherer{
128+
{
129+
Name: "d1",
130+
Kind: "dummy",
131+
Config: &dummyConfig{
132+
AlwaysFail: false,
133+
},
134+
},
135+
},
136+
InputPath: "/home",
137+
OutputPath: "/nothome",
138+
VenafiCloud: &VenafiCloudConfig{
139+
UploaderID: "test-agent",
140+
UploadPath: "/testing/path",
141+
},
142+
}
143+
144+
if diff, equal := messagediff.PrettyDiff(expected, loadedConfig); !equal {
145+
t.Errorf("Diff %s", diff)
146+
}
147+
}
148+
101149
func TestInvalidConfigError(t *testing.T) {
102150
configFileContents := `data-gatherers: "things"`
103151

pkg/agent/run.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ var Period time.Duration
3636
// OneShot flag causes agent to run once
3737
var OneShot bool
3838

39+
// VenafiCloudMode flag determines which format to load for config and credential type
40+
var VenafiCloudMode bool
41+
3942
// CredentialsPath is where the agent will try to loads the credentials. (Experimental)
4043
var CredentialsPath string
4144

@@ -221,7 +224,7 @@ func getConfiguration() (Config, client.Client) {
221224

222225
log.Printf("Loaded config: \n%s", dump)
223226

224-
var credentials *client.Credentials
227+
var credentials client.Credentials
225228
if CredentialsPath != "" {
226229
file, err = os.Open(CredentialsPath)
227230
if err != nil {
@@ -233,7 +236,11 @@ func getConfiguration() (Config, client.Client) {
233236
if err != nil {
234237
log.Fatalf("Failed to read credentials file: %v", err)
235238
}
236-
credentials, err = client.ParseCredentials(b)
239+
if VenafiCloudMode {
240+
credentials, err = client.ParseVenafiCredentials(b)
241+
} else {
242+
credentials, err = client.ParseOAuthCredentials(b)
243+
}
237244
if err != nil {
238245
log.Fatalf("Failed to parse credentials file: %s", err)
239246
}
@@ -247,8 +254,7 @@ func getConfiguration() (Config, client.Client) {
247254
var preflightClient client.Client
248255
switch {
249256
case credentials != nil:
250-
log.Println("A credentials file was specified, using oauth authentication.")
251-
preflightClient, err = client.NewOAuthClient(agentMetadata, credentials, baseURL)
257+
preflightClient, err = createCredentialClient(credentials, config, agentMetadata, baseURL)
252258
case APIToken != "":
253259
log.Println("An API token was specified, using API token authentication.")
254260
preflightClient, err = client.NewAPITokenClient(agentMetadata, APIToken, baseURL)
@@ -264,6 +270,24 @@ func getConfiguration() (Config, client.Client) {
264270
return config, preflightClient
265271
}
266272

273+
func createCredentialClient(credentials client.Credentials, config Config, agentMetadata *api.AgentMetadata, baseURL string) (client.Client, error) {
274+
switch creds := credentials.(type) {
275+
case *client.VenafiSvcAccountCredentials:
276+
log.Println("Venafi Cloud mode was specified, using Venafi Service Account authentication.")
277+
// check if config has Venafi Cloud data
278+
if config.VenafiCloud == nil {
279+
log.Fatalf("Failed to find config for venafi-cloud: required for Venafi Cloud mode")
280+
}
281+
return client.NewVenafiCloudClient(agentMetadata, creds, baseURL, config.VenafiCloud.UploaderID, config.VenafiCloud.UploadPath)
282+
283+
case *client.OAuthCredentials:
284+
log.Println("A credentials file was specified, using oauth authentication.")
285+
return client.NewOAuthClient(agentMetadata, creds, baseURL)
286+
default:
287+
return nil, errors.New("credentials file is in unknown format")
288+
}
289+
}
290+
267291
func gatherAndOutputData(config Config, preflightClient client.Client, dataGatherers map[string]datagatherer.DataGatherer) {
268292
var readings []*api.DataReading
269293

@@ -363,6 +387,18 @@ func postData(config Config, preflightClient client.Client, readings []*api.Data
363387

364388
log.Println("Running Agent...")
365389
log.Println("Posting data to:", baseURL)
390+
391+
if VenafiCloudMode {
392+
// orgID and clusterID are not required for Venafi Cloud auth
393+
err := preflightClient.PostDataReadings("", "", readings)
394+
if err != nil {
395+
return fmt.Errorf("post to server failed: %+v", err)
396+
}
397+
log.Println("Data sent successfully.")
398+
399+
return nil
400+
}
401+
366402
if config.OrganizationID == "" {
367403
data, err := json.Marshal(readings)
368404
if err != nil {
@@ -382,7 +418,7 @@ func postData(config Config, preflightClient client.Client, readings []*api.Data
382418
res, err := preflightClient.Post(path, bytes.NewBuffer(data))
383419

384420
if err != nil {
385-
return fmt.Errorf("Failed to post data: %+v", err)
421+
return fmt.Errorf("failed to post data: %+v", err)
386422
}
387423
if code := res.StatusCode; code < 200 || code >= 300 {
388424
errorContent := ""
@@ -392,19 +428,19 @@ func postData(config Config, preflightClient client.Client, readings []*api.Data
392428
}
393429
defer res.Body.Close()
394430

395-
return fmt.Errorf("Received response with status code %d. Body: %s", code, errorContent)
431+
return fmt.Errorf("received response with status code %d. Body: %s", code, errorContent)
396432
}
397433
log.Println("Data sent successfully.")
398434
return err
399435
}
400436

401437
if config.ClusterID == "" {
402-
return fmt.Errorf("Post to server failed: missing clusterID from agent configuration")
438+
return fmt.Errorf("post to server failed: missing clusterID from agent configuration")
403439
}
404440

405441
err := preflightClient.PostDataReadings(config.OrganizationID, config.ClusterID, readings)
406442
if err != nil {
407-
return fmt.Errorf("Post to server failed: %+v", err)
443+
return fmt.Errorf("post to server failed: %+v", err)
408444
}
409445
log.Println("Data sent successfully.")
410446

pkg/client/client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ type (
1515
PostDataReadings(orgID, clusterID string, readings []*api.DataReading) error
1616
Post(path string, body io.Reader) (*http.Response, error)
1717
}
18+
19+
// The Credentials interface describes methods for credential types to implement for verification.
20+
Credentials interface {
21+
IsClientSet() bool
22+
Validate() error
23+
}
1824
)
1925

2026
func fullURL(baseURL, path string) string {

pkg/client/client_oauth.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type (
2121
// The OAuthClient type is a Client implementation used to upload data readings to the Jetstack Secure platform
2222
// using OAuth as its authentication method.
2323
OAuthClient struct {
24-
credentials *Credentials
24+
credentials *OAuthCredentials
2525
accessToken *accessToken
2626
baseURL string
2727
agentMetadata *api.AgentMetadata
@@ -33,8 +33,8 @@ type (
3333
expirationDate time.Time
3434
}
3535

36-
// Credentials defines the format of the credentials.json file.
37-
Credentials struct {
36+
// OAuthCredentials defines the format of the credentials.json file.
37+
OAuthCredentials struct {
3838
// UserID is the ID or email for the user or service account.
3939
UserID string `json:"user_id"`
4040
// UserSecret is the secret for the user or service account.
@@ -68,8 +68,8 @@ func (t *accessToken) needsRenew() bool {
6868

6969
// NewOAuthClient returns a new instance of the OAuthClient type that will perform HTTP requests using OAuth to provide
7070
// authentication tokens to the backend API.
71-
func NewOAuthClient(agentMetadata *api.AgentMetadata, credentials *Credentials, baseURL string) (*OAuthClient, error) {
72-
if err := credentials.validate(); err != nil {
71+
func NewOAuthClient(agentMetadata *api.AgentMetadata, credentials *OAuthCredentials, baseURL string) (*OAuthClient, error) {
72+
if err := credentials.Validate(); err != nil {
7373
return nil, fmt.Errorf("cannot create OAuthClient: %v", err)
7474
}
7575
if baseURL == "" {
@@ -214,28 +214,28 @@ func (c *OAuthClient) renewAccessToken() error {
214214
return nil
215215
}
216216

217-
// ParseCredentials reads credentials into a struct used. Performs validations.
218-
func ParseCredentials(data []byte) (*Credentials, error) {
219-
var credentials Credentials
217+
// ParseOAuthCredentials reads credentials into an OAuthCredentials struct. Performs validations.
218+
func ParseOAuthCredentials(data []byte) (*OAuthCredentials, error) {
219+
var credentials OAuthCredentials
220220

221221
err := json.Unmarshal(data, &credentials)
222222
if err != nil {
223223
return nil, err
224224
}
225225

226-
if err = credentials.validate(); err != nil {
226+
if err = credentials.Validate(); err != nil {
227227
return nil, err
228228
}
229229

230230
return &credentials, nil
231231
}
232232

233233
// IsClientSet returns whether the client credentials are set or not.
234-
func (c *Credentials) IsClientSet() bool {
234+
func (c *OAuthCredentials) IsClientSet() bool {
235235
return c.ClientID != "" && c.ClientSecret != "" && c.AuthServerDomain != ""
236236
}
237237

238-
func (c *Credentials) validate() error {
238+
func (c *OAuthCredentials) Validate() error {
239239
var result *multierror.Error
240240

241241
if c == nil {

0 commit comments

Comments
 (0)