diff --git a/config/config.go b/config/config.go index 96442cf4..d777de7e 100644 --- a/config/config.go +++ b/config/config.go @@ -304,12 +304,12 @@ func (g *Github) CACertBundle() ([]byte, error) { return nil, nil } if _, err := os.Stat(g.CACertBundlePath); err != nil { - return nil, errors.Wrap(err, "accessing CA bundle") + return nil, fmt.Errorf("error accessing ca_cert_bundle: %w", err) } contents, err := os.ReadFile(g.CACertBundlePath) if err != nil { - return nil, errors.Wrap(err, "reading CA bundle") + return nil, fmt.Errorf("reading ca_cert_bundle: %w", err) } roots := x509.NewCertPool() @@ -338,6 +338,31 @@ func (g *Github) BaseEndpoint() string { } func (g *Github) Validate() error { + if g.Name == "" { + return fmt.Errorf("missing credentials name") + } + if g.Description == "" { + return fmt.Errorf("missing credentials description") + } + + if g.APIBaseURL != "" { + if _, err := url.ParseRequestURI(g.APIBaseURL); err != nil { + return fmt.Errorf("invalid api_base_url: %w", err) + } + } + + if g.UploadBaseURL != "" { + if _, err := url.ParseRequestURI(g.UploadBaseURL); err != nil { + return fmt.Errorf("invalid upload_base_url: %w", err) + } + } + + if g.BaseURL != "" { + if _, err := url.ParseRequestURI(g.BaseURL); err != nil { + return fmt.Errorf("invalid base_url: %w", err) + } + } + switch g.AuthType { case GithubAuthTypeApp: if err := g.App.Validate(); err != nil { diff --git a/config/config_test.go b/config/config_test.go index 8a2eac05..37f1e23e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -15,12 +15,15 @@ package config import ( + "context" "os" "path/filepath" "testing" "time" + "github.com/bradleyfalzon/ghinstallation/v2" "github.com/stretchr/testify/require" + "golang.org/x/oauth2" "github.com/cloudbase/garm/util/appdefaults" ) @@ -557,3 +560,383 @@ func TestNewConfigInvalidConfig(t *testing.T) { require.NotNil(t, err) require.Regexp(t, "validating config", err.Error()) } + +func TestGithubConfig(t *testing.T) { + cfg := getDefaultGithubConfig() + + tests := []struct { + name string + cfg Github + errString string + }{ + { + name: "Config is valid", + cfg: cfg[0], + errString: "", + }, + { + name: "BaseURL is invalid", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + BaseURL: "bogus", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + }, + errString: "invalid base_url: parse.*", + }, + { + name: "APIBaseURL is invalid", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + APIBaseURL: "bogus", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + }, + errString: "invalid api_base_url: parse.*", + }, + { + name: "UploadBaseURL is invalid", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + UploadBaseURL: "bogus", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + }, + errString: "invalid upload_base_url: parse.*", + }, + { + name: "BaseURL is set and is valid", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + BaseURL: "https://github.example.com/", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + }, + }, + { + name: "APIBaseURL is set and is valid", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + APIBaseURL: "https://github.example.com/api/v3", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + }, + }, + { + name: "UploadBaseURL is set and is valid", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + UploadBaseURL: "https://github.example.com/uploads", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + }, + }, + { + name: "OAuth2Token is empty", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + }, + errString: "missing github oauth2 token", + }, + { + name: "Name is empty", + cfg: Github{ + Name: "", + Description: "dummy github credentials", + OAuth2Token: "bogus", + }, + errString: "missing credentials name", + }, + { + name: "Description is empty", + cfg: Github{ + Name: "dummy_creds", + Description: "", + OAuth2Token: "bogus", + }, + errString: "missing credentials description", + }, + { + name: "OAuth token is set in the PAT section", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + }, + }, + { + name: "OAuth token is empty in the PAT section", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "", + }, + }, + errString: "missing github oauth2 token", + }, + { + name: "Valid App section", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypeApp, + App: GithubApp{ + AppID: 1, + InstallationID: 99, + PrivateKeyPath: "../testdata/certs/srv-key.pem", + }, + }, + }, + { + name: "AppID is missing", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypeApp, + App: GithubApp{ + AppID: 0, + InstallationID: 99, + PrivateKeyPath: "../testdata/certs/srv-key.pem", + }, + }, + errString: "missing app_id", + }, + { + name: "InstallationID is missing", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypeApp, + App: GithubApp{ + AppID: 1, + InstallationID: 0, + PrivateKeyPath: "../testdata/certs/srv-key.pem", + }, + }, + errString: "missing installation_id", + }, + { + name: "PrivateKeyPath is missing", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypeApp, + App: GithubApp{ + AppID: 1, + InstallationID: 99, + PrivateKeyPath: "", + }, + }, + errString: "missing private_key_path", + }, + { + name: "PrivateKeyPath is invalid", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypeApp, + App: GithubApp{ + AppID: 1, + InstallationID: 99, + PrivateKeyPath: "/i/dont/exist", + }, + }, + errString: "invalid github app config: error accessing private_key_path: stat /i/dont/exist: no such file or directory", + }, + { + name: "PrivateKeyPath is not a valid RSA private key", + cfg: Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "", + AuthType: GithubAuthTypeApp, + App: GithubApp{ + AppID: 1, + InstallationID: 99, + PrivateKeyPath: "../testdata/certs/srv-pub.pem", + }, + }, + errString: "invalid github app config: parsing private_key_path: asn1: structure error:.*", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.cfg.Validate() + if tc.errString == "" { + require.Nil(t, err) + } else { + require.NotNil(t, err) + require.Regexp(t, tc.errString, err.Error()) + } + }) + } +} + +func TestGithubAPIEndpoint(t *testing.T) { + cfg := getDefaultGithubConfig() + + require.Equal(t, "https://api.github.com/", cfg[0].APIEndpoint()) +} + +func TestGithubAPIEndpointIsSet(t *testing.T) { + cfg := getDefaultGithubConfig() + cfg[0].APIBaseURL = "https://github.example.com/api/v3" + + require.Equal(t, "https://github.example.com/api/v3", cfg[0].APIEndpoint()) +} + +func TestUploadEndpoint(t *testing.T) { + cfg := getDefaultGithubConfig() + + require.Equal(t, "https://uploads.github.com/", cfg[0].UploadEndpoint()) +} + +func TestUploadEndpointIsSet(t *testing.T) { + cfg := getDefaultGithubConfig() + cfg[0].UploadBaseURL = "https://github.example.com/uploads" + + require.Equal(t, "https://github.example.com/uploads", cfg[0].UploadEndpoint()) +} + +func TestGithubBaseURL(t *testing.T) { + cfg := getDefaultGithubConfig() + + require.Equal(t, "https://github.com", cfg[0].BaseEndpoint()) +} + +func TestGithubBaseURLIsSet(t *testing.T) { + cfg := getDefaultGithubConfig() + cfg[0].BaseURL = "https://github.example.com" + + require.Equal(t, "https://github.example.com", cfg[0].BaseEndpoint()) +} + +func TestCACertBundle(t *testing.T) { + cfg := Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "bogus", + CACertBundlePath: "../testdata/certs/srv-pub.pem", + } + + cert, err := cfg.CACertBundle() + require.Nil(t, err) + require.NotNil(t, cert) +} + +func TestCACertBundleInvalidPath(t *testing.T) { + cfg := Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "bogus", + CACertBundlePath: "/i/dont/exist", + } + + cert, err := cfg.CACertBundle() + require.NotNil(t, err) + require.EqualError(t, err, "error accessing ca_cert_bundle: stat /i/dont/exist: no such file or directory") + require.Nil(t, cert) +} + +func TestCACertBundleInvalidFile(t *testing.T) { + cfg := Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "bogus", + CACertBundlePath: "../testdata/config.toml", + } + + cert, err := cfg.CACertBundle() + require.NotNil(t, err) + require.EqualError(t, err, "failed to parse CA cert bundle") + require.Nil(t, cert) +} + +func TestGithubHTTPClientDeprecatedPAT(t *testing.T) { + cfg := Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + OAuth2Token: "bogus", + } + + client, err := cfg.HTTPClient(context.Background()) + require.Nil(t, err) + require.NotNil(t, client) + + transport, ok := client.Transport.(*oauth2.Transport) + require.True(t, ok) + require.NotNil(t, transport) +} + +func TestGithubHTTPClientPAT(t *testing.T) { + cfg := Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + AuthType: GithubAuthTypePAT, + PAT: GithubPAT{ + OAuth2Token: "bogus", + }, + } + + client, err := cfg.HTTPClient(context.Background()) + require.Nil(t, err) + require.NotNil(t, client) + + transport, ok := client.Transport.(*oauth2.Transport) + require.True(t, ok) + require.NotNil(t, transport) +} + +func TestGithubHTTPClientApp(t *testing.T) { + cfg := Github{ + Name: "dummy_creds", + Description: "dummy github credentials", + AuthType: GithubAuthTypeApp, + App: GithubApp{ + AppID: 1, + InstallationID: 99, + PrivateKeyPath: "../testdata/certs/srv-key.pem", + }, + } + + client, err := cfg.HTTPClient(context.Background()) + require.Nil(t, err) + require.NotNil(t, client) + + transport, ok := client.Transport.(*ghinstallation.Transport) + require.True(t, ok) + require.NotNil(t, transport) +}