From d81a22de837829d83256a59fa31dc5fecff20893 Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Wed, 3 Apr 2024 23:19:36 -0600 Subject: [PATCH 1/6] feat(svc-account): Enables tokenURL. Removes tenantID Enables the use of tokenURL for VCP service account authentication. The tenantID attribute has been removed. --- cmd/vcert/args.go | 4 +- cmd/vcert/commands.go | 4 +- cmd/vcert/config.go | 14 +-- cmd/vcert/datareader.go | 25 +++-- cmd/vcert/envVars.go | 14 +-- cmd/vcert/flags.go | 25 ++--- cmd/vcert/validators.go | 4 +- cmd/vcert/validatorsCloud.go | 18 ++-- examples/tlspc-svc-account/main.go | 28 +++--- pkg/endpoint/authentication.go | 16 +-- pkg/playbook/app/domain/authentication.go | 42 ++++---- .../app/domain/authentication_test.go | 98 ++++++++++++++++++- pkg/playbook/app/domain/connection.go | 6 +- pkg/playbook/app/domain/error.go | 4 +- pkg/playbook/app/vcertutil/vcertutil.go | 18 +--- pkg/util/utils.go | 1 - pkg/venafi/cloud/cloud.go | 13 +++ pkg/venafi/cloud/connector.go | 14 +-- 18 files changed, 225 insertions(+), 123 deletions(-) diff --git a/cmd/vcert/args.go b/cmd/vcert/args.go index 39c1cea5..991f720c 100644 --- a/cmd/vcert/args.go +++ b/cmd/vcert/args.go @@ -44,8 +44,8 @@ var ( type commandFlags struct { apiKey string - vaasTenantID string - externalJWT string + idPJWT string + tokenURL string appInfo string audience string caDN string diff --git a/cmd/vcert/commands.go b/cmd/vcert/commands.go index eb5b5a5b..4bb51011 100644 --- a/cmd/vcert/commands.go +++ b/cmd/vcert/commands.go @@ -874,11 +874,11 @@ func getVaaSCredentials(vaasConnector *cloud.Connector, cfg *vcert.Config) error fmt.Println("api_key_expires: ", apiKey.ValidityEndDateString) } // Request access token - } else if cfg.Credentials.ExternalIdPJWT != "" && cfg.Credentials.TenantID != "" { + } else if cfg.Credentials.IdPJWT != "" && cfg.Credentials.IdentityProvider != nil && cfg.Credentials.IdentityProvider.TokenURL != "" { // Request access token from VaaS service account tokenResponse, err := vaasConnector.GetAccessToken(cfg.Credentials) if err != nil { - return fmt.Errorf("failed to request access token from VaaS: %w", err) + return fmt.Errorf("failed to request access token from VCP: %w", err) } if flags.credFormat == "json" { diff --git a/cmd/vcert/config.go b/cmd/vcert/config.go index 96202b27..68743910 100644 --- a/cmd/vcert/config.go +++ b/cmd/vcert/config.go @@ -161,12 +161,14 @@ func buildConfigVaaS(flags *commandFlags) (*vcert.Config, error) { ConnectorType: endpoint.ConnectorTypeCloud, BaseUrl: flags.url, Credentials: &endpoint.Authentication{ - User: flags.email, - Password: flags.password, - AccessToken: flags.token, - APIKey: flags.apiKey, - TenantID: flags.vaasTenantID, - ExternalIdPJWT: flags.externalJWT, + User: flags.email, + Password: flags.password, + AccessToken: flags.token, + APIKey: flags.apiKey, + IdPJWT: flags.idPJWT, + IdentityProvider: &endpoint.OAuthProvider{ + TokenURL: flags.tokenURL, + }, }, }, nil } diff --git a/cmd/vcert/datareader.go b/cmd/vcert/datareader.go index a8269e3e..82363234 100644 --- a/cmd/vcert/datareader.go +++ b/cmd/vcert/datareader.go @@ -29,7 +29,7 @@ func readData(commandName string) error { fileName := flags.distinguishedName[5:] bytes, err := os.ReadFile(fileName) if err != nil { - return fmt.Errorf("Failed to read Certificate DN: %s", err) + return fmt.Errorf("failed to read Certificate DN: %s", err) } flags.distinguishedName = strings.TrimSpace(string(bytes)) } @@ -37,7 +37,7 @@ func readData(commandName string) error { fileName := flags.keyPassword[5:] bytes, err := os.ReadFile(fileName) if err != nil { - return fmt.Errorf("Failed to read password from file: %s", err) + return fmt.Errorf("failed to read password from file: %s", err) } flags.keyPassword = strings.TrimSpace(string(bytes)) } @@ -46,21 +46,30 @@ func readData(commandName string) error { certFileName := flags.thumbprint[5:] flags.thumbprint, err = readThumbprintFromFile(certFileName) if err != nil { - return fmt.Errorf("Failed to read certificate fingerprint: %s", err) + return fmt.Errorf("failed to read certificate fingerprint: %s", err) } } - if strings.HasPrefix(flags.externalJWT, filePrefix) { - fileName := flags.externalJWT[5:] + if strings.HasPrefix(flags.idPJWT, filePrefix) { + fileName := flags.idPJWT[5:] bytes, err := os.ReadFile(fileName) if err != nil { - return fmt.Errorf("failed to read external JWT from file: %w", err) + return fmt.Errorf("failed to read IdP JWT from file: %w", err) } - flags.externalJWT = strings.TrimSpace(string(bytes)) + flags.idPJWT = strings.TrimSpace(string(bytes)) + } + + if strings.HasPrefix(flags.tokenURL, filePrefix) { + fileName := flags.tokenURL[5:] + bytes, err := os.ReadFile(fileName) + if err != nil { + return fmt.Errorf("failed to read token URL from file: %w", err) + } + flags.tokenURL = strings.TrimSpace(string(bytes)) } if err = readPasswordsFromInputFlags(commandName, &flags); err != nil { - return fmt.Errorf("Failed to read password from input: %s", err) + return fmt.Errorf("failed to read password from input: %s", err) } return nil } diff --git a/cmd/vcert/envVars.go b/cmd/vcert/envVars.go index dc4e52d9..e0347105 100644 --- a/cmd/vcert/envVars.go +++ b/cmd/vcert/envVars.go @@ -8,8 +8,8 @@ const ( vCertZone = "VCERT_ZONE" vCertToken = "VCERT_TOKEN" // #nosec G101 vCertApiKey = "VCERT_APIKEY" // #nosec G101 - vCertTenantID = "VCERT_TENANT_ID" - vCertExternalJWT = "VCERT_EXTERNAL_JWT" + vCertIdPJWT = "VCERT_IDP_JWT" + vCertTokenURL = "VCERT_TOKEN_URL" vCertTrustBundle = "VCERT_TRUST_BUNDLE" vcertUser = "VCERT_USER" vcertPassword = "VCERT_PASSWORD" @@ -52,13 +52,13 @@ var ( FlagName: "-k", }, { - EnvVarName: vCertTenantID, - Destination: &flags.vaasTenantID, - FlagName: "--tenant-id", + EnvVarName: vCertTokenURL, + Destination: &flags.tokenURL, + FlagName: "--token-url", }, { - EnvVarName: vCertExternalJWT, - Destination: &flags.externalJWT, + EnvVarName: vCertIdPJWT, + Destination: &flags.idPJWT, FlagName: "--external-jwt", }, { diff --git a/cmd/vcert/flags.go b/cmd/vcert/flags.go index c93da5e5..80a018c3 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -42,6 +42,13 @@ var ( Aliases: []string{"u"}, } + flagTokenUrl = &cli.StringFlag{ + Name: "token-url", + Usage: "REQUIRED/VCP. The URL of the token service." + + "\n\t\tExample: --token-url https://api.venafi.cloud/v1/oauth2/v2.0/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/token", + Destination: &flags.tokenURL, + } + flagUrlDeprecated = &cli.StringFlag{ Name: "venafi-saas-url", Usage: "", @@ -57,16 +64,10 @@ var ( Aliases: []string{"k"}, } - flagTenantID = &cli.StringFlag{ - Name: "tenant-id", - Usage: "REQUIRED/VaaS. The ID of your tenant/company in VaaS. Use it along --external-jwt to retrieve an access token for VaaS", - Destination: &flags.vaasTenantID, - } - - flagExternalJWT = &cli.StringFlag{ - Name: "external-jwt", - Usage: "REQUIRED/VaaS. The JWT of the Identity Provider associated to the service account to be used. Use it along --tenant-id flag to retrieve an access token for VaaS", - Destination: &flags.externalJWT, + flagIdPJWT = &cli.StringFlag{ + Name: "idp-jwt", + Usage: "REQUIRED/VCP. The JWT of the Identity Provider associated to the service account to be used. Use it along --token-url flag to retrieve an access token for VCP", + Destination: &flags.idPJWT, } flagDeviceURL = &cli.StringFlag{ @@ -864,8 +865,8 @@ var ( flagAudience, flagDeviceURL, commonFlags, - flagTenantID, - flagExternalJWT, + flagTokenUrl, + flagIdPJWT, )) checkCredFlags = sortedFlags(flagsApppend( diff --git a/cmd/vcert/validators.go b/cmd/vcert/validators.go index f605dd9c..fce5bfee 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -103,8 +103,8 @@ func validateConnectionFlags(commandName string) error { flags.password != "" || flags.token != "" || flags.url != "" || - flags.vaasTenantID != "" || - flags.externalJWT != "" || + flags.tokenURL != "" || + flags.idPJWT != "" || flags.testMode { return fmt.Errorf("connection details cannot be specified with flags when --config is used") } diff --git a/cmd/vcert/validatorsCloud.go b/cmd/vcert/validatorsCloud.go index 0ad138f2..dee0ab0c 100644 --- a/cmd/vcert/validatorsCloud.go +++ b/cmd/vcert/validatorsCloud.go @@ -11,18 +11,18 @@ func validateConnectionFlagsCloud(commandName string) error { //getcred command if commandName == commandGetCredName { - tenantIDPresent := flags.vaasTenantID != "" || getPropertyFromEnvironment(vCertTenantID) != "" - externalJWTPresent := flags.externalJWT != "" || getPropertyFromEnvironment(vCertExternalJWT) != "" - svcAccountPresent := tenantIDPresent && externalJWTPresent + tokenURLPresent := flags.tokenURL != "" || getPropertyFromEnvironment(vCertTokenURL) != "" + jwtPresent := flags.idPJWT != "" || getPropertyFromEnvironment(vCertIdPJWT) != "" + svcAccountPresent := tokenURLPresent && jwtPresent emailPresent := flags.email != "" - if tenantIDPresent && !externalJWTPresent { - return fmt.Errorf("missing jwt for service account authentication. Set the jwt using --external-jwt flag") + if tokenURLPresent && !jwtPresent { + return fmt.Errorf("missing jwt for service account authentication. Set the jwt using --idp-jwt flag") } - advice := "Use --tenant-id/--external-jwt for authentication or --email for registration" + advice := "Use --token-url/--idp-jwt for authentication or --email for registration" if !svcAccountPresent && !emailPresent { - return fmt.Errorf("missing flags for Venafi as a Service authentication. %s", advice) + return fmt.Errorf("missing flags for Venafi Cloud Platform authentication. %s", advice) } return nil @@ -38,9 +38,5 @@ func validateConnectionFlagsCloud(commandName string) error { return fmt.Errorf("missing flags for Venafi as a Service authentication. %s", advice) } - if apiKeyPresent && tokenPresent { - return fmt.Errorf("multiple methods set for Venafi as a Service authentication. %s", advice) - } - return nil } diff --git a/examples/tlspc-svc-account/main.go b/examples/tlspc-svc-account/main.go index ad8229bb..3dcf5212 100644 --- a/examples/tlspc-svc-account/main.go +++ b/examples/tlspc-svc-account/main.go @@ -13,10 +13,10 @@ import ( ) const ( - TlspcUrl = "TLSPC_URL" - TlspcZone = "TLSPC_ZONE" - TlspcTenantId = "TLSPC_TENANT_ID" - TlspcJwt = "TLSPC_JWT" + vcpURL = "VCP_URL" + vcpZone = "VCP_ZONE" + vcpTokenURL = "VCP_TOKEN_URL" + vcpJWT = "VCP_JWT" envVarNotSet = "environment variable not set: %s" @@ -27,19 +27,19 @@ const ( func main() { // URL can be nil if using production TLSPC - url := os.Getenv(TlspcUrl) + url := os.Getenv(vcpURL) - zone, found := os.LookupEnv(TlspcZone) + zone, found := os.LookupEnv(vcpZone) if !found { - log.Fatalf(envVarNotSet, TlspcZone) + log.Fatalf(envVarNotSet, vcpZone) } - tenantID, found := os.LookupEnv(TlspcTenantId) + tokenURL, found := os.LookupEnv(vcpTokenURL) if !found { - log.Fatalf(envVarNotSet, TlspcTenantId) + log.Fatalf(envVarNotSet, vcpTokenURL) } - jwt, found := os.LookupEnv(TlspcJwt) + jwt, found := os.LookupEnv(vcpJWT) if !found { - log.Fatalf(envVarNotSet, TlspcJwt) + log.Fatalf(envVarNotSet, vcpJWT) } userAgent := fmt.Sprintf("%s/%s %s", name, version, util.DefaultUserAgent) @@ -48,8 +48,10 @@ func main() { BaseUrl: url, Zone: zone, Credentials: &endpoint.Authentication{ - TenantID: tenantID, - ExternalIdPJWT: jwt, + IdPJWT: jwt, + IdentityProvider: &endpoint.OAuthProvider{ + TokenURL: tokenURL, + }, }, UserAgent: &userAgent, } diff --git a/pkg/endpoint/authentication.go b/pkg/endpoint/authentication.go index 30047fb6..6230f9de 100644 --- a/pkg/endpoint/authentication.go +++ b/pkg/endpoint/authentication.go @@ -19,22 +19,21 @@ package endpoint // Authentication provides a struct for authentication data. Either specify User and Password for Trust Protection Platform // or Firefly or ClientId and ClientSecret for Firefly or specify an APIKey for TLS Protect Cloud. type Authentication struct { - //TPP Auth methods - // User and password + // TPP Auth methods + // user and password User string `yaml:"user,omitempty"` //**DEPRECATED** Use access/refresh token or client certificate instead Password string `yaml:"password,omitempty"` //**DEPRECATED** Use access/refresh token or client certificate instead - // Tokens + // tokens AccessToken string `yaml:"accessToken,omitempty"` RefreshToken string `yaml:"refreshToken,omitempty"` - // Client certificate + // client certificate ClientPKCS12 bool `yaml:"-"` - //TLSPC Auth methods + // VCP Auth methods // API key APIKey string `yaml:"apiKey,omitempty"` // Service account - TenantID string `yaml:"tenantId,omitempty"` - ExternalIdPJWT string `yaml:"externalJWT,omitempty"` + IdPJWT string `yaml:"idPJWT,omitempty"` // IDP Auth method ClientId string `yaml:"clientId,omitempty"` @@ -46,7 +45,8 @@ type Authentication struct { // OAuthProvider provides a struct for the OAuth 2.0 providers information type OAuthProvider struct { + // OIDC Auth methods DeviceURL string `yaml:"-"` - TokenURL string `yaml:"tokenURL,omitempty"` + TokenURL string `yaml:"tokenURL,omitempty"` // This attribute is also used by VCP service account Audience string `yaml:"audience,omitempty"` } diff --git a/pkg/playbook/app/domain/authentication.go b/pkg/playbook/app/domain/authentication.go index 7885693a..d5e90db4 100644 --- a/pkg/playbook/app/domain/authentication.go +++ b/pkg/playbook/app/domain/authentication.go @@ -27,13 +27,13 @@ import ( const ( accessToken = "accessToken" apiKey = "apiKey" - tenantID = "tenantId" - externalJWT = "externalJWT" + idPJWT = "idPJWT" clientID = "clientId" clientSecret = "clientSecret" refreshToken = "refreshToken" p12Task = "p12Task" scope = "scope" + idP = "idP" idPTokenURL = "tokenURL" idPAudience = "audience" ) @@ -55,12 +55,6 @@ func (a Authentication) MarshalYAML() (interface{}, error) { if a.APIKey != "" { values[apiKey] = a.APIKey } - if a.TenantID != "" { - values[tenantID] = a.TenantID - } - if a.ExternalIdPJWT != "" { - values[externalJWT] = a.ExternalIdPJWT - } if a.ClientId != "" { values[clientID] = a.ClientId } @@ -68,12 +62,17 @@ func (a Authentication) MarshalYAML() (interface{}, error) { values[clientSecret] = a.ClientSecret } if a.IdentityProvider != nil { - if a.IdentityProvider.TokenURL != "" { - values[idPTokenURL] = a.IdentityProvider.TokenURL - } + idpMap := make(map[string]interface{}) if a.IdentityProvider.Audience != "" { - values[idPAudience] = a.IdentityProvider.Audience + idpMap[idPAudience] = a.IdentityProvider.Audience } + if a.IdentityProvider.TokenURL != "" { + idpMap[idPTokenURL] = a.IdentityProvider.TokenURL + } + values[idP] = idpMap + } + if a.IdPJWT != "" { + values[idPJWT] = a.IdPJWT } if a.RefreshToken != "" { values[refreshToken] = a.RefreshToken @@ -102,18 +101,15 @@ func (a *Authentication) UnmarshalYAML(value *yaml.Node) error { if val, found := authMap[apiKey]; found { a.APIKey = val.(string) } - if val, found := authMap[tenantID]; found { - a.TenantID = val.(string) - } - if val, found := authMap[externalJWT]; found { - a.ExternalIdPJWT = val.(string) - } if val, found := authMap[clientID]; found { a.ClientId = val.(string) } if val, found := authMap[clientSecret]; found { a.ClientSecret = val.(string) } + if val, found := authMap[idPJWT]; found { + a.IdPJWT = val.(string) + } if val, found := authMap[refreshToken]; found { a.RefreshToken = val.(string) } @@ -124,11 +120,13 @@ func (a *Authentication) UnmarshalYAML(value *yaml.Node) error { a.Scope = val.(string) } - provider, err := unmarshallIdP(authMap) - if err != nil { - return err + if val, found := authMap[idP]; found { + provider, err := unmarshallIdP(val) + if err != nil { + return err + } + a.IdentityProvider = provider } - a.IdentityProvider = provider return nil } diff --git a/pkg/playbook/app/domain/authentication_test.go b/pkg/playbook/app/domain/authentication_test.go index 48d41b04..a8e13f1a 100644 --- a/pkg/playbook/app/domain/authentication_test.go +++ b/pkg/playbook/app/domain/authentication_test.go @@ -19,17 +19,109 @@ package domain import ( "testing" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/venafi" "github.com/stretchr/testify/suite" + "gopkg.in/yaml.v3" ) +const examplePlaybook = `certificateTasks: + - name: foo +config: + connection: + credentials: + accessToken: "123456" + apiKey: xyz789 + clientId: clientID + clientSecret: clientSecret + idP: + audience: some audience + tokenURL: some.token.url + idPJWT: tokenJWT + p12Task: foo + refreshToken: abcdef + scope: noScope + insecure: true + platform: VAAS + trustBundle: some/path.txt + url: foo.bar.com +` + type AuthenticationSuite struct { suite.Suite } -func (s *AuthenticationSuite) SetupTest() { - -} +func (s *AuthenticationSuite) SetupTest() {} func TestAuthentication(t *testing.T) { suite.Run(t, new(AuthenticationSuite)) } + +func (s *AuthenticationSuite) TestAuthentication_MarshalIdentityProvider() { + p := Playbook{ + CertificateTasks: CertificateTasks{ + CertificateTask{ + Name: "foo", + }, + }, + Config: Config{ + Connection: Connection{ + Credentials: Authentication{ + Authentication: endpoint.Authentication{ + AccessToken: "123456", + RefreshToken: "abcdef", + APIKey: "xyz789", + IdPJWT: "tokenJWT", + ClientId: "clientID", + ClientSecret: "clientSecret", + Scope: "noScope", + IdentityProvider: &endpoint.OAuthProvider{ + TokenURL: "some.token.url", + Audience: "some audience", + }, + }, + P12Task: "foo", + }, + Insecure: true, + Platform: venafi.TLSPCloud, + TrustBundlePath: "some/path.txt", + URL: "foo.bar.com", + }, + }, + } + + data, err := yaml.Marshal(p) + s.NoError(err) + s.NotNil(data) + s.Equal([]byte(examplePlaybook), data) + //err = os.WriteFile("test", data, 0644) + //s.NoError(err) + +} + +func (s *AuthenticationSuite) TestAuthentication_UnmarshalIdentityProvider() { + playbook := NewPlaybook() + err := yaml.Unmarshal([]byte(examplePlaybook), &playbook) + s.NoError(err) + s.Equal(1, len(playbook.CertificateTasks)) + s.Equal("foo", playbook.CertificateTasks[0].Name) + + s.NotNil(playbook.Config.Connection) + s.True(playbook.Config.Connection.Insecure) + s.Equal(venafi.TLSPCloud, playbook.Config.Connection.Platform) + s.Equal("some/path.txt", playbook.Config.Connection.TrustBundlePath) + s.Equal("foo.bar.com", playbook.Config.Connection.URL) + + s.NotNil(playbook.Config.Connection.Credentials) + s.Equal("foo", playbook.Config.Connection.Credentials.P12Task) + s.Equal("123456", playbook.Config.Connection.Credentials.AccessToken) + s.Equal("abcdef", playbook.Config.Connection.Credentials.RefreshToken) + s.Equal("xyz789", playbook.Config.Connection.Credentials.APIKey) + s.Equal("tokenJWT", playbook.Config.Connection.Credentials.IdPJWT) + s.Equal("clientID", playbook.Config.Connection.Credentials.ClientId) + s.Equal("clientSecret", playbook.Config.Connection.Credentials.ClientSecret) + s.Equal("noScope", playbook.Config.Connection.Credentials.Scope) + s.NotNil(playbook.Config.Connection.Credentials.IdentityProvider) + s.Equal("some.token.url", playbook.Config.Connection.Credentials.IdentityProvider.TokenURL) + s.Equal("some audience", playbook.Config.Connection.Credentials.IdentityProvider.Audience) +} diff --git a/pkg/playbook/app/domain/connection.go b/pkg/playbook/app/domain/connection.go index 71ccd669..6368e1bb 100644 --- a/pkg/playbook/app/domain/connection.go +++ b/pkg/playbook/app/domain/connection.go @@ -111,7 +111,7 @@ func isValidVaaS(c Connection) (bool, error) { } svcaccount := false - if c.Credentials.TenantID != "" { + if c.Credentials.IdentityProvider != nil && c.Credentials.IdentityProvider.TokenURL != "" { svcaccount = true } @@ -123,8 +123,8 @@ func isValidVaaS(c Connection) (bool, error) { return true, nil } - if c.Credentials.ExternalIdPJWT == "" { - return false, ErrNoExternalJWT + if c.Credentials.IdPJWT == "" { + return false, ErrNoIdPJWT } return true, nil diff --git a/pkg/playbook/app/domain/error.go b/pkg/playbook/app/domain/error.go index 459c2633..627e41a2 100644 --- a/pkg/playbook/app/domain/error.go +++ b/pkg/playbook/app/domain/error.go @@ -84,6 +84,6 @@ var ( ErrNoClientId = fmt.Errorf("no cliendId defined. Firefly platform requires a clientId to request OAuth2 token") // ErrNoIdentityProviderURL is thrown when platform is Firefly and no config.credentials.tokenURL is defined to request an OAuth2 Token ErrNoIdentityProviderURL = fmt.Errorf("no tokenURL defined in credentials. tokenURL is required to request OAuth2 token") - // ErrNoExternalJWT is thrown when platform is TLSPC/VAAS, a tenantId has been passed but no config.credentials.externalJWT is set - ErrNoExternalJWT = fmt.Errorf("no externalJWT defined in credentials. externalJWT is required to request an access token from TLSPC") + // ErrNoIdPJWT is thrown when platform is TLSPC/VAAS, a tenantId has been passed but no config.credentials.externalJWT is set + ErrNoIdPJWT = fmt.Errorf("no idPJWT defined in credentials. idPJWT is required to request an access token from VCP") ) diff --git a/pkg/playbook/app/vcertutil/vcertutil.go b/pkg/playbook/app/vcertutil/vcertutil.go index 4a4f7a35..ab383950 100644 --- a/pkg/playbook/app/vcertutil/vcertutil.go +++ b/pkg/playbook/app/vcertutil/vcertutil.go @@ -124,29 +124,17 @@ func buildVCertAuthentication(playbookAuth domain.Authentication) (*endpoint.Aut } vcertAuth.APIKey = apiKey - // Cloud tenant ID - tenantID := playbookAuth.TenantID - if strings.HasPrefix(tenantID, filePrefix) { - data, err := readFile(tenantID[offset:]) - if err != nil { - attribute := fmt.Sprintf("%s.tenantId", attrPrefix) - return nil, fmt.Errorf("failed to read value [%s] from authentication attribute: %w", attribute, err) - } - tenantID = strings.TrimSpace(string(data)) - } - vcertAuth.TenantID = tenantID - // Cloud JWT - jwt := playbookAuth.ExternalIdPJWT + jwt := playbookAuth.IdPJWT if strings.HasPrefix(jwt, filePrefix) { data, err := readFile(jwt[offset:]) if err != nil { - attribute := fmt.Sprintf("%s.externalJWT", attrPrefix) + attribute := fmt.Sprintf("%s.idPJWT", attrPrefix) return nil, fmt.Errorf("failed to read value [%s] from authentication attribute: %w", attribute, err) } jwt = strings.TrimSpace(string(data)) } - vcertAuth.ExternalIdPJWT = jwt + vcertAuth.IdPJWT = jwt // Access token accessToken := playbookAuth.AccessToken diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 7f4be465..2f62784b 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -119,7 +119,6 @@ func ArrayContainsString(s []string, e string) bool { } func NormalizeUrl(url string) string { - modified := strings.ToLower(url) reg := regexp.MustCompile("^http(|s)://") if reg.FindStringIndex(modified) == nil { diff --git a/pkg/venafi/cloud/cloud.go b/pkg/venafi/cloud/cloud.go index c2593fd2..b8366323 100644 --- a/pkg/venafi/cloud/cloud.go +++ b/pkg/venafi/cloud/cloud.go @@ -26,6 +26,7 @@ import ( "log" "net" "net/http" + "net/url" "sort" "strconv" "strings" @@ -1030,3 +1031,15 @@ func isWildCard(cnRegex []string) bool { } return false } + +func getServiceAccountTokenURL(rawURL string) (string, error) { + normalizedURL := util.NormalizeUrl(rawURL) + // removing trailing slash from util.NormalizeURL function + normalizedURL, _ = strings.CutSuffix(normalizedURL, "/") + _, err := url.ParseRequestURI(normalizedURL) + if err != nil { + return "", fmt.Errorf("token url error: %w", err) + } + + return normalizedURL, nil +} diff --git a/pkg/venafi/cloud/connector.go b/pkg/venafi/cloud/connector.go index 057af170..c0cbc34e 100644 --- a/pkg/venafi/cloud/connector.go +++ b/pkg/venafi/cloud/connector.go @@ -141,8 +141,8 @@ func (c *Connector) Authenticate(auth *endpoint.Authentication) error { return nil } - //2. JWT and tenantID. use it to request new access token - if auth.TenantID != "" && auth.ExternalIdPJWT != "" { + //2. JWT and token URL. use it to request new access token + if auth.IdPJWT != "" && auth.IdentityProvider != nil && auth.IdentityProvider.TokenURL != "" { tokenResponse, err := c.GetAccessToken(auth) if err != nil { return err @@ -868,17 +868,19 @@ func normalizeURL(url string) (normalizedURL string, err error) { } func (c *Connector) GetAccessToken(auth *endpoint.Authentication) (*TLSPCAccessTokenResponse, error) { - if auth == nil { + if auth == nil || auth.IdentityProvider == nil || auth.IdentityProvider.TokenURL == "" { return nil, fmt.Errorf("failed to authenticate: missing credentials") } - url := c.getURL(urlServiceAccountToken) - url = fmt.Sprintf(url, auth.TenantID) + url, err := getServiceAccountTokenURL(auth.IdentityProvider.TokenURL) + if err != nil { + return nil, fmt.Errorf("failed to authenticate: %w", err) + } body := netUrl.Values{} body.Set("grant_type", "client_credentials") body.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - body.Set("client_assertion", auth.ExternalIdPJWT) + body.Set("client_assertion", auth.IdPJWT) r, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body.Encode())) if err != nil { From 556f0eefd12a6631ff49cdb88deca192b21dfa97 Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Wed, 3 Apr 2024 23:36:13 -0600 Subject: [PATCH 2/6] feat(svc-account): Adds VCP as alias for VaaS/TLSPC --- pkg/venafi/cloud/cloud.go | 6 ++---- pkg/venafi/platform.go | 8 +++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/venafi/cloud/cloud.go b/pkg/venafi/cloud/cloud.go index b8366323..5a909475 100644 --- a/pkg/venafi/cloud/cloud.go +++ b/pkg/venafi/cloud/cloud.go @@ -1033,13 +1033,11 @@ func isWildCard(cnRegex []string) bool { } func getServiceAccountTokenURL(rawURL string) (string, error) { - normalizedURL := util.NormalizeUrl(rawURL) // removing trailing slash from util.NormalizeURL function - normalizedURL, _ = strings.CutSuffix(normalizedURL, "/") - _, err := url.ParseRequestURI(normalizedURL) + _, err := url.ParseRequestURI(rawURL) if err != nil { return "", fmt.Errorf("token url error: %w", err) } - return normalizedURL, nil + return rawURL, nil } diff --git a/pkg/venafi/platform.go b/pkg/venafi/platform.go index 7830270e..ecfa11bf 100644 --- a/pkg/venafi/platform.go +++ b/pkg/venafi/platform.go @@ -42,13 +42,15 @@ const ( strPlatformFake = "FAKE" strPlatformFirefly = "FIREFLY" strPlatformTPP = "TPP" - strPlatformVaaS = "VAAS" + strPlatformVCP = "VCP" strPlatformUnknown = "Unknown" // alias for TPP strPlatformTLSPDC = "TLSPDC" - // alias for VaaS + // alias for VCP strPlatformTLSPC = "TLSPC" + // alias for VCP + strPlatformVaaS = "VAAS" //NOTE: For now OIDC will be taken as an alias for Firefly //given Firefly implements the logic to get an OAuth 2.0 //Access token but OIDC will be available independently of Firefly @@ -104,7 +106,7 @@ func GetPlatformType(platformString string) Platform { return Firefly case strPlatformTPP, strPlatformTLSPDC: return TPP - case strPlatformVaaS, strPlatformTLSPC: + case strPlatformVCP, strPlatformVaaS, strPlatformTLSPC: return TLSPCloud default: return Undefined From 528ef2fb5f6164ff8dde39b09540d1f088bbbcf6 Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Thu, 4 Apr 2024 17:09:31 -0600 Subject: [PATCH 3/6] feat(svc-account): Updated docs with service-account authentication details --- CHANGELOG.md | 27 +- Jenkinsfile | 58 -- README-CLI-CLOUD.md | 342 +++++---- README-PLAYBOOK.md | 62 +- README-POLICY-SPEC.md | 151 ++-- README.md | 148 ++-- cmd/vcert/certificates.go | 721 +++++++++++++++++ cmd/vcert/commands.go | 1401 +--------------------------------- cmd/vcert/credentials.go | 170 +++++ cmd/vcert/main.go | 40 +- cmd/vcert/policies.go | 218 ++++++ cmd/vcert/sshcertificates.go | 421 ++++++++++ pkg/venafi/platform.go | 13 +- 13 files changed, 2003 insertions(+), 1769 deletions(-) delete mode 100644 Jenkinsfile create mode 100644 cmd/vcert/certificates.go create mode 100644 cmd/vcert/credentials.go create mode 100644 cmd/vcert/policies.go create mode 100644 cmd/vcert/sshcertificates.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 379347d6..fc1fc38d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,29 @@ +## 5.6.2 (April 4th, 2024) +VCert SDK: +- Removes `TenantID` from `endpoint.Authentication` struct +- `cloud.Connector` will use `endpoint.Authentication.OAuthProvider.TokenURL` instead of building the URL (using the +`tenantID`) to obtain the access token + +VCert CLI: +- Removes `--tenant-id` flag for `getcred` command +- Adds `--token-url` flag for `getcred` command + +VCert Playbook: +- Removes `tenantId` attribute from `config.connection.credentials` object +- Now uses `config.connection.credentials.idP.tokenURL` for Venafi Control Plane service account authentication + ## 5.6.1 (April 2nd, 2024) +VCert SDK: - Adds UserAgent header to api requests for TPP, Cloud and Firefly connectors - Adds functionality to convert a Platform type to a ConnectorType enum ## 5.6.0 (March 28th, 2024) - - Adds support for service account authentication in TLSPC connector - - Adds new attributes to CLI `getcred` command: `tenant-id` and `external-jwt` for service account authentication - - Adds support for service account authentication to VCert playbooks \ No newline at end of file +VCert SDK: +- Adds support for service account authentication in Cloud connector + +VCert CLI: +- Adds new attributes to `getcred` command: `tenant-id` and `external-jwt` for Venafi Control Plane (VCP) service +account authentication + +VCert playbook: +- Adds support for service account authentication to VCert playbooks \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index d85acede..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env groovy -node("jnode-vcert") { - - String goPath = "/go/src/github.com/Venafi/vcert/v4" - - stage('Checkout') { - checkout scm - } - - stage("Build") { - docker.image("golang:1.9").inside("-v ${pwd()}:${goPath} -u root") { - sh "cd ${goPath} && make build" - } - } - - stage("Run Tests") { - parallel( - test: { - docker.image("golang:1.9").inside("-v ${pwd()}:${goPath} -u root") { - sh "cd ${goPath} && go get ./... && make test" - } - }, - e2eTPP: { - docker.image("golang:1.9").inside("-v ${pwd()}:${goPath} -u root") { - sh "cd ${goPath} && go get ./... && make tpp_test" - } - }, - e2eCloud: { - docker.image("golang:1.9").inside("-v ${pwd()}:${goPath} -u root") { - sh "cd ${goPath} && go get ./... && make cloud_test" - } - }, - testCLI: { - sh "make cucumber" - } - ) - } - - stage("Deploy") { - archiveArtifacts artifacts: 'bin/**/*', fingerprint: true - } - - stage("Publish") { - cifsPublisher paramPublish: null, masterNodeName:'', alwaysPublishFromMaster: false, - continueOnError: false, - failOnError: false, - publishers: [[ - configName: 'buildsDev', - transfers: [[ - cleanRemote: true, excludes: '*/obj/,/node_modules/,/_src/,/_config/,/_sassdocs/', - flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', - remoteDirectory: env.JOB_NAME, remoteDirectorySDF: false, - removePrefix: 'bin', - sourceFiles: 'bin/' - ]], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true - ]] - } -} diff --git a/README-CLI-CLOUD.md b/README-CLI-CLOUD.md index bec076d8..626df72a 100644 --- a/README-CLI-CLOUD.md +++ b/README-CLI-CLOUD.md @@ -1,16 +1,18 @@ ![Venafi](https://raw.githubusercontent.com/Venafi/.github/master/images/Venafi_logo.png) [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![Community Supported](https://img.shields.io/badge/Support%20Level-Community-brightgreen) -![Compatible with TPP 17.3+ & VaaS](https://img.shields.io/badge/Compatibility-TPP%2017.3+%20%26%20VaaS-f9a90c) +![Compatible with TPP 17.3+ & VCP](https://img.shields.io/badge/Compatibility-TPP%2017.3+%20%26%20VCP-f9a90c) _**This open source project is community-supported.** To report a problem or share an idea, use **[Issues](../../issues)**; and if you have a suggestion for fixing the issue, please include those details, too. In addition, use **[Pull Requests](../../pulls)** to contribute actual bug fixes or proposed enhancements. We welcome and appreciate all contributions. Got questions or want to discuss something with our team? **[Join us on Slack](https://join.slack.com/t/venafi-integrations/shared_invite/zt-i8fwc379-kDJlmzU8OiIQOJFSwiA~dg)**!_ -# VCert CLI for Venafi as a Service +# VCert CLI for Venafi Control Plane -Venafi VCert is a command line tool designed to generate keys and simplify certificate acquisition, eliminating the need to write code that's required to interact with the Venafi REST API. VCert is available in 32- and 64-bit versions for Linux, Windows, and macOS. +Venafi VCert is a command line tool designed to generate keys and simplify certificate acquisition, eliminating the +need to write code that's required to interact with the Venafi REST API. VCert is available in 32- and 64-bit versions +for Linux, Windows, and macOS. This article applies to the latest version of VCert CLI, which you can [download here](https://github.com/Venafi/vcert/releases/latest). @@ -24,7 +26,7 @@ brew install venafi/tap/vcert Use these links to quickly jump to a relevant section lower on this page: -- [VCert CLI for Venafi as a Service](#vcert-cli-for-venafi-as-a-service) +- [VCert CLI for Venafi as a Service](#vcert-cli-for-venafi-control-plane) - [Quick Links](#quick-links) - [Prerequisites](#prerequisites) - [General Command Line Parameters](#general-command-line-parameters) @@ -42,16 +44,17 @@ Use these links to quickly jump to a relevant section lower on this page: ## Prerequisites -Review these prerequistes to get started. You'll need the following: - -1. Verify that the Venafi as a Service REST API at [https://api.venafi.cloud](https://api.venafi.cloud/vaas) or -[https://api.venafi.eu](https://api.venafi.eu/vaas) (if you have an EU account) is accessible from the system where VCert will be run. -2. You have successfully registered for a Venafi as a Service account, have been granted at least the -"Resource Owner" role, and know your API key. You can use the `getcred` action to -[register and obtain an API key](#registering-and-obtaining-an-api-key), but you will need an administrator -to update your role if there are already 3 or more users registered for your company in Venafi as a Service. -Alternatively, you have configured a service account, the service account has been granted the "Resource Owner" role, -you know the `tenant ID` and have obtained a `JWT` from the Identity Provider associated to the service-account. +Review these prerequisites to get started. You'll need the following: + +1. Verify that the Venafi Control Plane REST API at [https://api.venafi.cloud](https://api.venafi.cloud/vaas) or +[https://api.venafi.eu](https://api.eu.venafi.cloud/vaas) (if you have an EU account) is accessible from the system where +VCert will be run. +2. You have successfully registered for a Venafi Control Plane account, have been granted at least the "Resource Owner" +role, and know your API key. You can use the `getcred` action to +[register and obtain an API key](#registering-and-obtaining-an-api-key), but you will need an administrator to update +your role if there are already 3 or more users registered for your company in Venafi Control Plane. Alternatively, you +have configured a service account, the service account has been granted the "Resource Owner" role, you have the +`token URL` and have obtained a `JWT` from the Identity Provider associated to the service-account. 3. A CA Account and Issuing Template exist and have been configured with: 1. Recommended Settings values for: 1. Organizational Unit (OU) @@ -63,43 +66,45 @@ you know the `tenant ID` and have obtained a `JWT` from the Identity Provider as 1. (Recommended) Limits Common Name and Subject Alternative Names that are allowed by your organization 2. (Recommended) Restricts the Key Length to 2048 or higher 3. (Recommended) Does not allow Private Key Reuse -4. An Application exists where you are among the owners, -and you know the Application Name. +4. An Application exists where you are among the owners, and you know the Application Name. 5. An Issuing Template is assigned to the Application, and you know its API Alias. -> 📌 **NOTE**: if you're just testing, you can skip the last 3 items. Simply specify "Default" for the issuing template alias portion -> of your zone (e.g., "My Application\Default") and an application with the name you specified will be automatically created for you. + +> 📌 **NOTE**: if you're just testing, you can skip the last 3 items. Simply specify "Default" for the issuing template +> alias portion of your zone (e.g. "My Application\Default") and an application with the name you specified will be +> automatically created for you. ## General Command Line Parameters The following options apply to the `enroll`, `pickup`, and `renew` actions: -| Flag | Description | -|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--config` | Use to specify INI configuration file containing connection details. Available parameters: `cloud_apikey`, `cloud_zone`, `trust_bundle`, `test_mode`. | -| `-k` or `--apiKey` | Use to specify your API key for Venafi as a Service.
Example: -k aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee | -| `--no-prompt` | Use to exclude password prompts. If you enable the prompt and you enter incorrect information, an error is displayed. This option is useful with scripting. | -| `-p` or `--platform` | Use to specify Venafi as a Service as the platform of choice to connect. Accepted values are: `TLSPC` and `VAAS`. | -| `-t` or `--token` | Use to specify an access token for Venafi as a Service. You need to set `--platform TLSPC` in order to use access tokens for Venafi as a Service. | -| `--test-mode` | Use to test operations without connecting to Venafi as a Service. This option is useful for integration tests where the test environment does not have access to Venafi as a Service. Default is false. | -| `--test-mode-delay` | Use to specify the maximum number of seconds for the random test-mode connection delay. Default is 15 (seconds). | -| `--timeout` | Use to specify the maximum amount of time to wait in seconds for a certificate to be processed by VaaS. Default is 120 (seconds). | -| `--trust-bundle` | Use to specify a file with PEM formatted certificates to be used as trust anchors when communicating with VaaS. Generally not needed because VaaS is secured by a publicly trusted certificate but it may be needed if your organization requires VCert to traverse a proxy server. VCert uses the trust store of your operating system for this purpose if not specified.
Example: `--trust-bundle /path-to/bundle.pem` | -| `-u` or `--url` | Use to specify the URL of the Venafi as a Service API server. If it's omitted, then VCert will use [https://api.venafi.cloud](https://api.venafi.cloud/vaas) as API server.
Example: `-u https://api.venafi.eu` | -| `--verbose` | Use to increase the level of logging detail, which is helpful when troubleshooting issues. | +| Flag | Description | +|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--config` | Use to specify INI configuration file containing connection details. Available parameters: `cloud_apikey`, `cloud_zone`, `trust_bundle`, `test_mode`. | +| `-k` or `--apiKey` | Use to specify your API key for Venafi Control Plane.
Example: -k aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee | +| `--no-prompt` | Use to exclude password prompts. If you enable the prompt and you enter incorrect information, an error is displayed. This option is useful with scripting. | +| `-p` or `--platform` | Use to specify Venafi Control Plane as the platform of choice to connect. Accepted value is `vcp`, case-insensitive. | +| `-t` or `--token` | Use to specify an access token for Venafi Control Plane. You need to set `--platform vcp` or `-p vcp` in order to use access tokens for Venafi Control Plane. | +| `--test-mode` | Use to test operations without connecting to Venafi Control Plane. This option is useful for integration tests where the test environment does not have access to Venafi Control Plane. Default is false. | +| `--test-mode-delay` | Use to specify the maximum number of seconds for the random test-mode connection delay. Default is 15 (seconds). | +| `--timeout` | Use to specify the maximum amount of time to wait in seconds for a certificate to be processed by Venafi Control Plane. Default is 120 (seconds). | +| `--trust-bundle` | Use to specify a file with PEM formatted certificates to be used as trust anchors when communicating with Venafi Control Plane. Generally not needed because VCP is secured by a publicly trusted certificate, but it may be needed if your organization requires VCert to traverse a proxy server. VCert uses the trust store of your operating system for this purpose if not specified.
Example: `--trust-bundle /path-to/bundle.pem` | +| `-u` or `--url` | Use to specify the URL of the Venafi Control Plane API server. If it's omitted, then VCert will use [https://api.venafi.cloud](https://api.venafi.cloud/vaas) as API server.
Example: `-u https://api.venafi.eu` | +| `--verbose` | Use to increase the level of logging detail, which is helpful when troubleshooting issues. | ### Environment Variables VCert supports supplying flag values using environment variables: -| Attribute | Flag | Environment Variable | -|----------------------------|--------------------|----------------------| -| API key | `-k` or `--apiKey` | `VCERT_APIKEY` | -| JWT from Identity Provider | `external-jwt` | `VCERT_EXTERNAL_JWT` | -| Venafi platform | `--platform` | `VCERT_PLATFORM` | -| Tenant ID | `--tenant-id` | `VCERT_TENANT_ID` | -| Venafi as a Service token | `-t` or `--token` | `VCERT_TOKEN` | -| Venafi as a Service URL | `-u` or `--url` | `VCERT_URL` | -| Zone | `-z` or `--zone` | `VCERT_ZONE` | +| Attribute | Flag | Environment Variable | +|--------------------------------|--------------------|----------------------| +| API key | `-k` or `--apiKey` | `VCERT_APIKEY` | +| JWT from Identity Provider | `--idp-jwt` | `VCERT_IDP_JWT` | +| Venafi Control Plane token | `-t` or `--token` | `VCERT_TOKEN` | +| Venafi Control Plane token URL | `--token-url` | `VCERT_TOKEN_URL` | +| Venafi Control Plane URL | `-u` or `--url` | `VCERT_URL` | +| Venafi platform | `--platform` | `VCERT_PLATFORM` | +| Zone | `-z` or `--zone` | `VCERT_ZONE` | + ## Certificate Request Parameters API key: @@ -108,35 +113,35 @@ vcert enroll -k --cn -z --cn -z +vcert enroll -p vcp -t --cn -z ``` Options: -|         Command         | Description | -|---------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--app-info` | Use to identify the application requesting the certificate with details like vendor name and vendor product.
Example: `--app-info "Venafi VCert CLI"` | -| `--cert-file` | Use to specify the name and location of an output file that will contain only the end-entity certificate.
Example: `--cert-file /path-to/example.crt` | -| `--chain` | Use to include the certificate chain in the output, and to specify where to place it in the file.
Options: `root-last` (default), `root-first`, `ignore` | -| `--chain-file` | Use to specify the name and location of an output file that will contain only the root and intermediate certificates applicable to the end-entity certificate. | -| `--cn` | Use to specify the common name (CN). This is required for Enrollment. | -| `--csr` | Use to specify the CSR and private key location. Options: `local` (default), `service`, `file`
- local: private key and CSR will be generated locally
- service: private key and CSR will be generated by a VSatellite in Venafi as a Service
- file: CSR will be read from a file by name
Example: `--csr file:/path-to/example.req` | -| `--file` | Use to specify a name and location of an output file that will contain the private key and certificates when they are not written to their own files using `--key-file`, `--cert-file`, and/or `--chain-file`.
Example: `--file /path-to/keycert.pem` | -| `--format` | Use to specify the output format. The `--file` option must be used with the PKCS#12 and JKS formats to specify the keystore file. JKS format also requires `--jks-alias` and at least one password (see `--key-password` and `--jks-password`)
Options: `pem` (default), `legacy-pem`, `json`, `pkcs12`, `legacy-pkcs12` (analogous to OpenSSL 3.x -legacy flag), `jks` | -| `--jks-alias` | Use to specify the alias of the entry in the JKS file when `--format jks` is used | -| `--jks-password` | Use to specify the keystore password of the JKS file when `--format jks` is used. If not specified, the `--key-password` value is used for both the key and store passwords | -| `--key-curve` | Use to specify the elliptic curve for key generation when `--key-type` is ECDSA.
Options: `p256` (default), `p384`, `p521` | -| `--key-file` | Use to specify the name and location of an output file that will contain only the private key.
Example: `--key-file /path-to/example.key` | -| `--key-password` | Use to specify a password for encrypting the private key. For a non-encrypted private key, specify `--no-prompt` without specifying this option. You can specify the password using one of three methods: at the command line, when prompted, or by using a password file.
Example: `--key-password file:/path-to/passwd.txt` | -| `--key-size` | Use to specify a key size for RSA keys. Default is 2048. | -| `--key-type` | Use to specify the key algorithm.
Options: `rsa` (default), `ecdsa` | -| `--no-pickup` | Use to disable the feature of VCert that repeatedly tries to retrieve the issued certificate. When this is used you must run VCert again in pickup mode to retrieve the certificate that was requested. | -| `--pickup-id-file` | Use to specify a file name where the unique identifier for the certificate will be stored for subsequent use by pickup, renew, and revoke actions. Default is to write the Pickup ID to STDOUT. | -| `--san-dns` | Use to specify a DNS Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-dns one.example.com` `--san-dns two.example.com` | -| `--san-email` | Use to specify an Email Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-email me@example.com` `--san-email you@example.com` | -| `--san-ip` | Use to specify an IP Address Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-ip 10.20.30.40` `--san-ip 192.168.192.168` | -| `--san-uri` | Use to specify a Uniform Resource Indicator Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-uri spiffe://workload1.example.com` `--san-uri spiffe://workload2.example.com` | -| `--valid-days` | Use to specify the number of days a certificate needs to be valid.
Example: `--valid-days 30` | -| `-z` | Use to specify the name of the Application to which the certificate will be assigned and the API Alias of the Issuing Template that will handle the certificate request.
Example: `-z "Business App\\Enterprise CIT"` | +| Command | Description | +|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--app-info` | Use to identify the application requesting the certificate with details like vendor name and vendor product.
Example: `--app-info "Venafi VCert CLI"` | +| `--cert-file` | Use to specify the name and location of an output file that will contain only the end-entity certificate.
Example: `--cert-file /path-to/example.crt` | +| `--chain` | Use to include the certificate chain in the output, and to specify where to place it in the file.
Options: `root-last` (default), `root-first`, `ignore` | +| `--chain-file` | Use to specify the name and location of an output file that will contain only the root and intermediate certificates applicable to the end-entity certificate. | +| `--cn` | Use to specify the common name (CN). This is required for Enrollment. | +| `--csr` | Use to specify the CSR and private key location. Options: `local` (default), `service`, `file`
- local: private key and CSR will be generated locally
- service: private key and CSR will be generated by a VSatellite in Venafi as a Service
- file: CSR will be read from a file by name
Example: `--csr file:/path-to/example.req` | +| `--file` | Use to specify a name and location of an output file that will contain the private key and certificates when they are not written to their own files using `--key-file`, `--cert-file`, and/or `--chain-file`.
Example: `--file /path-to/keycert.pem` | +| `--format` | Use to specify the output format. The `--file` option must be used with the PKCS#12 and JKS formats to specify the keystore file. JKS format also requires `--jks-alias` and at least one password (see `--key-password` and `--jks-password`)
Options: `pem` (default), `legacy-pem`, `json`, `pkcs12`, `legacy-pkcs12` (analogous to OpenSSL 3.x -legacy flag), `jks` | +| `--jks-alias` | Use to specify the alias of the entry in the JKS file when `--format jks` is used | +| `--jks-password` | Use to specify the keystore password of the JKS file when `--format jks` is used. If not specified, the `--key-password` value is used for both the key and store passwords | +| `--key-curve` | Use to specify the elliptic curve for key generation when `--key-type` is ECDSA.
Options: `p256` (default), `p384`, `p521` | +| `--key-file` | Use to specify the name and location of an output file that will contain only the private key.
Example: `--key-file /path-to/example.key` | +| `--key-password` | Use to specify a password for encrypting the private key. For a non-encrypted private key, specify `--no-prompt` without specifying this option. You can specify the password using one of three methods: at the command line, when prompted, or by using a password file.
Example: `--key-password file:/path-to/passwd.txt` | +| `--key-size` | Use to specify a key size for RSA keys. Default is 2048. | +| `--key-type` | Use to specify the key algorithm.
Options: `rsa` (default), `ecdsa` | +| `--no-pickup` | Use to disable the feature of VCert that repeatedly tries to retrieve the issued certificate. When this is used you must run VCert again in pickup mode to retrieve the certificate that was requested. | +| `--pickup-id-file` | Use to specify a file name where the unique identifier for the certificate will be stored for subsequent use by pickup, renew, and revoke actions. Default is to write the Pickup ID to STDOUT. | +| `--san-dns` | Use to specify a DNS Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-dns one.example.com` `--san-dns two.example.com` | +| `--san-email` | Use to specify an Email Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-email me@example.com` `--san-email you@example.com` | +| `--san-ip` | Use to specify an IP Address Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-ip 10.20.30.40` `--san-ip 192.168.192.168` | +| `--san-uri` | Use to specify a Uniform Resource Indicator Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-uri spiffe://workload1.example.com` `--san-uri spiffe://workload2.example.com` | +| `--valid-days` | Use to specify the number of days a certificate needs to be valid.
Example: `--valid-days 30` | +| `-z` | Use to specify the name of the Application to which the certificate will be assigned and the API Alias of the Issuing Template that will handle the certificate request.
Example: `-z "Business App\\Enterprise CIT"` | ## Certificate Retrieval Parameters API key: @@ -145,19 +150,19 @@ vcert pickup -k [--pickup-id | --pickup-id-file [--pickup-id | --pickup-id-file ] +vcert pickup -p vcp -t [--pickup-id | --pickup-id-file ] ``` Options: -|         Command         | Description | -|---------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--cert-file` | Use to specify the name and location of an output file that will contain only the end-entity certificate.
Example: `--cert-file /path-to/example.crt` | -| `--chain` | Use to include the certificate chain in the output, and to specify where to place it in the file.
Options: `root-last` (default), `root-first`, `ignore` | -| `--chain-file` | Use to specify the name and location of an output file that will contain only the root and intermediate certificates applicable to the end-entity certificate. | -| `--file` | Use to specify a name and location of an output file that will contain certificates when they are not written to their own files using `--cert-file` and/or `--chain-file`.
Example: `--file /path-to/keycert.pem` | -| `--format` | Use to specify the output format.
Options: `pem` (default), `json` | -| `--pickup-id` | Use to specify the unique identifier of the certificate returned by the enroll or renew actions if `--no-pickup` was used or a timeout occurred. Required when `--pickup-id-file` is not specified. | -| `--pickup-id-file` | Use to specify a file name that contains the unique identifier of the certificate returned by the enroll or renew actions if --no-pickup was used or a timeout occurred. Required when `--pickup-id` is not specified. | +| Command | Description | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--cert-file` | Use to specify the name and location of an output file that will contain only the end-entity certificate.
Example: `--cert-file /path-to/example.crt` | +| `--chain` | Use to include the certificate chain in the output, and to specify where to place it in the file.
Options: `root-last` (default), `root-first`, `ignore` | +| `--chain-file` | Use to specify the name and location of an output file that will contain only the root and intermediate certificates applicable to the end-entity certificate. | +| `--file` | Use to specify a name and location of an output file that will contain certificates when they are not written to their own files using `--cert-file` and/or `--chain-file`.
Example: `--file /path-to/keycert.pem` | +| `--format` | Use to specify the output format.
Options: `pem` (default), `json` | +| `--pickup-id` | Use to specify the unique identifier of the certificate returned by the enroll or renew actions if `--no-pickup` was used or a timeout occurred. Required when `--pickup-id-file` is not specified. | +| `--pickup-id-file` | Use to specify a file name that contains the unique identifier of the certificate returned by the enroll or renew actions if --no-pickup was used or a timeout occurred. Required when `--pickup-id` is not specified. | ## Certificate Renewal Parameters API key: @@ -166,35 +171,35 @@ vcert renew -k [--id | --thumbprint ] ``` Access token: ``` -vcert renew -p TLSPC -t [--id | --thumbprint ] +vcert renew -p vcp -t [--id | --thumbprint ] ``` Options: -|         Command         | Description | -|---------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--cert-file` | Use to specify the name and location of an output file that will contain only the end-entity certificate.
Example: `--cert-file /path-to/example.crt` | -| `--chain` | Use to include the certificate chain in the output, and to specify where to place it in the file.
Options: `root-last` (default), `root-first`, `ignore` | -| `--chain-file` | Use to specify the name and location of an output file that will contain only the root and intermediate certificates applicable to the end-entity certificate. | -| `--cn` | Use to specify the common name (CN). This is required for Enrollment. | -| `--csr` | Use to specify the CSR and private key location. Options: `local` (default), `service`, `file`
- local: private key and CSR will be generated locally
- service: private key and CSR will be generated by a VSatellite in Venafi as a Service
- file: CSR will be read from a file by name
Example: `--csr file:/path-to/example.req` | -| `--file` | Use to specify a name and location of an output file that will contain the private key and certificates when they are not written to their own files using `--key-file`, `--cert-file`, and/or `--chain-file`.
Example: `--file /path-to/keycert.pem` | -| `--format` | Use to specify the output format. The `--file` option must be used with the PKCS#12 and JKS formats to specify the keystore file. JKS format also requires `--jks-alias` and at least one password (see `--key-password` and `--jks-password`)
Options: `pem` (default), `legacy-pem`, `json`, `pkcs12`, `legacy-pkcs12` (analogous to OpenSSL 3.x -legacy flag), `jks` | -| `--id` | Use to specify the unique identifier of the certificate returned by the enroll or renew actions. Value may be specified as a string or read from a file by using the file: prefix.
Example: `--id file:cert_id.txt` | -| `--jks-alias` | Use to specify the alias of the entry in the JKS file when `--format jks` is used | -| `--jks-password` | Use to specify the keystore password of the JKS file when `--format jks` is used. If not specified, the `--key-password` value is used for both the key and store passwords | -| `--key-curve` | Use to specify the elliptic curve for key generation when `--key-type` is ECDSA.
Options: `p256` (default), `p384`, `p521` | -| `--key-file` | Use to specify the name and location of an output file that will contain only the private key.
Example: `--key-file /path-to/example.key` | -| `--key-password` | Use to specify a password for encrypting the private key. For a non-encrypted private key, specify `--no-prompt` without specifying this option. You can specify the password using one of three methods: at the command line, when prompted, or by using a password file. | -| `--key-size` | Use to specify a key size for RSA keys. Default is 2048. | -| `--key-type` | Use to specify the key algorithm.
Options: `rsa` (default), `ecdsa` | -| `--no-pickup` | Use to disable the feature of VCert that repeatedly tries to retrieve the issued certificate. When this is used you must run VCert again in pickup mode to retrieve the certificate that was requested. | -| `--omit-sans` | Ignore SANs in the previous certificate when preparing the renewal request. Workaround for CAs that forbid any SANs even when the SANs match those the CA automatically adds to the issued certificate. | -| `--pickup-id-file` | Use to specify a file name where the unique identifier for the certificate will be stored for subsequent use by `pickup`, `renew`, and `revoke` actions. By default it is written to STDOUT. | -| `--san-dns` | Use to specify a DNS Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-dns one.example.com` `--san-dns two.example.com` | -| `--san-email` | Use to specify an Email Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-email me@example.com` `--san-email you@example.com` | -| `--san-ip` | Use to specify an IP Address Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-ip 10.20.30.40` `--san-ip 192.168.192.168` | -| `--san-uri` | Use to specify a Uniform Resource Indicator Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-uri spiffe://workload1.example.com` `--san-uri spiffe://workload2.example.com` | -| `--thumbprint` | Use to specify the SHA1 thumbprint of the certificate to renew. Value may be specified as a string or read from the certificate file using the `file:` prefix. | +| Command | Description | +|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--cert-file` | Use to specify the name and location of an output file that will contain only the end-entity certificate.
Example: `--cert-file /path-to/example.crt` | +| `--chain` | Use to include the certificate chain in the output, and to specify where to place it in the file.
Options: `root-last` (default), `root-first`, `ignore` | +| `--chain-file` | Use to specify the name and location of an output file that will contain only the root and intermediate certificates applicable to the end-entity certificate. | +| `--cn` | Use to specify the common name (CN). This is required for Enrollment. | +| `--csr` | Use to specify the CSR and private key location. Options: `local` (default), `service`, `file`
- local: private key and CSR will be generated locally
- service: private key and CSR will be generated by a VSatellite in Venafi as a Service
- file: CSR will be read from a file by name
Example: `--csr file:/path-to/example.req` | +| `--file` | Use to specify a name and location of an output file that will contain the private key and certificates when they are not written to their own files using `--key-file`, `--cert-file`, and/or `--chain-file`.
Example: `--file /path-to/keycert.pem` | +| `--format` | Use to specify the output format. The `--file` option must be used with the PKCS#12 and JKS formats to specify the keystore file. JKS format also requires `--jks-alias` and at least one password (see `--key-password` and `--jks-password`)
Options: `pem` (default), `legacy-pem`, `json`, `pkcs12`, `legacy-pkcs12` (analogous to OpenSSL 3.x -legacy flag), `jks` | +| `--id` | Use to specify the unique identifier of the certificate returned by the enroll or renew actions. Value may be specified as a string or read from a file by using the file: prefix.
Example: `--id file:cert_id.txt` | +| `--jks-alias` | Use to specify the alias of the entry in the JKS file when `--format jks` is used | +| `--jks-password` | Use to specify the keystore password of the JKS file when `--format jks` is used. If not specified, the `--key-password` value is used for both the key and store passwords | +| `--key-curve` | Use to specify the elliptic curve for key generation when `--key-type` is ECDSA.
Options: `p256` (default), `p384`, `p521` | +| `--key-file` | Use to specify the name and location of an output file that will contain only the private key.
Example: `--key-file /path-to/example.key` | +| `--key-password` | Use to specify a password for encrypting the private key. For a non-encrypted private key, specify `--no-prompt` without specifying this option. You can specify the password using one of three methods: at the command line, when prompted, or by using a password file. | +| `--key-size` | Use to specify a key size for RSA keys. Default is 2048. | +| `--key-type` | Use to specify the key algorithm.
Options: `rsa` (default), `ecdsa` | +| `--no-pickup` | Use to disable the feature of VCert that repeatedly tries to retrieve the issued certificate. When this is used you must run VCert again in pickup mode to retrieve the certificate that was requested. | +| `--omit-sans` | Ignore SANs in the previous certificate when preparing the renewal request. Workaround for CAs that forbid any SANs even when the SANs match those the CA automatically adds to the issued certificate. | +| `--pickup-id-file` | Use to specify a file name where the unique identifier for the certificate will be stored for subsequent use by `pickup`, `renew`, and `revoke` actions. By default it is written to STDOUT. | +| `--san-dns` | Use to specify a DNS Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-dns one.example.com` `--san-dns two.example.com` | +| `--san-email` | Use to specify an Email Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-email me@example.com` `--san-email you@example.com` | +| `--san-ip` | Use to specify an IP Address Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-ip 10.20.30.40` `--san-ip 192.168.192.168` | +| `--san-uri` | Use to specify a Uniform Resource Indicator Subject Alternative Name. To specify more than one, simply repeat this parameter for each value.
Example: `--san-uri spiffe://workload1.example.com` `--san-uri spiffe://workload2.example.com` | +| `--thumbprint` | Use to specify the SHA1 thumbprint of the certificate to renew. Value may be specified as a string or read from the certificate file using the `file:` prefix. | ## Certificate Retire Parameters API key: @@ -203,14 +208,14 @@ vcert retire -k [--id | --thumbprint ] ``` Access Token: ``` -vcert retire -p TLSPC -t [--id | --thumbprint ] +vcert retire -p vcp -t [--id | --thumbprint ] ``` Options: -|         Command         | Description | -|---------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--id` | Use to specify the unique identifier of the certificate to retire. Value may be specified as a string or read from a file using the `file:` prefix. | -| `--thumbprint` | Use to specify the SHA1 thumbprint of the certificate to retire. Value may be specified as a string or read from the certificate file using the `file:` prefix. | +| Command | Description | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--id` | Use to specify the unique identifier of the certificate to retire. Value may be specified as a string or read from a file using the `file:` prefix. | +| `--thumbprint` | Use to specify the SHA1 thumbprint of the certificate to retire. Value may be specified as a string or read from the certificate file using the `file:` prefix. | ## Parameters for Applying Certificate Policy API key: @@ -219,28 +224,37 @@ vcert setpolicy -k -z --file ``` Access token: ``` -vcert setpolicy -p TLSPC -t -z --file +vcert setpolicy -p vcp -t -z --file ``` Options: -|         Command         | Description | -|---------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| -| `--file` | Use to specify the location of the required file that contains a JSON or YAML certificate policy specification. | -| `--verify` | Use to verify that a policy specification is valid. `-k` and `-z` are ignored with this option. | +| Command | Description | +|------------|-----------------------------------------------------------------------------------------------------------------| +| `--file` | Use to specify the location of the required file that contains a JSON or YAML certificate policy specification. | +| `--verify` | Use to verify that a policy specification is valid. `-k` and `-z` are ignored with this option. | Notes: - The Venafi certificate policy specification is documented in detail [here](README-POLICY-SPEC.md). - The PKI Administrator role is required to apply certificate policy. -- Policy (Issuing Template rules) and defaults (Issuing Template recommended settings) revert to their default state if they are not present in a policy specification applied by this action. -- If the application or issuing template specified by the `-z` zone parameter do not exist, this action will attempt to create them with the calling user as the application owner. -- This action can be used to simply create a new application and/or default issuing template by indicating those names with the `-z` zone parameter and applying a file that contains an empty policy (i.e. `{}`). -- If the issuing template specified by the `-z` zone parameter is not already assigned to the application, this action will attempt to make that assignment. -- The syntax for the `certificateAuthority` policy value is _"CA Account Type\\CA Account Name\\CA Product Name"_ (e.g. "DIGICERT\\DigiCert SSL Plus\\ssl_plus"). -When not present in the policy specification, `certificateAuthority` defaults to "BUILTIN\\Built-In CA\\Default Product". -- The `autoInstalled` policy/defaults does not apply as automated installation of certificates by VaaS is not yet supported. -- The `ellipticCurves` and `serviceGenerated` policy/defaults (`keyPair`) do not apply as ECC and central key generation are not yet supported by VaaS. -- The `ipAllowed`, `emailAllowed`, `uriAllowed`, and `upnAllowed` policy (`subjectAltNames`) do not apply as those SAN types are not yet supported by VaaS. -- If undefined key/value pairs are included in the policy specification, they will be silently ignored by this action. This would include keys that are misspelled. +- Policy (Issuing Template rules) and defaults (Issuing Template recommended settings) revert to their default state if +they are not present in a policy specification applied by this action. +- If the application or issuing template specified by the `-z` zone parameter do not exist, this action will attempt to +create them with the calling user as the application owner. +- This action can be used to simply create a new application and/or default issuing template by indicating those names +with the `-z` zone parameter and applying a file that contains an empty policy (i.e. `{}`). +- If the issuing template specified by the `-z` zone parameter is not already assigned to the application, this action +will attempt to make that assignment. +- The syntax for the `certificateAuthority` policy value is _CA Account Type\\CA Account Name\\CA Product Name_ +(e.g. `DIGICERT\\DigiCert SSL Plus\\ssl_plus`). +When not present in the policy specification, `certificateAuthority` defaults to `BUILTIN\\Built-In CA\\Default Product`. +- The `autoInstalled` policy/defaults does not apply as automated installation of certificates by Venafi Control Plane +is not yet supported. +- The `ellipticCurves` and `serviceGenerated` policy/defaults (`keyPair`) do not apply as ECC and central key generation +are not yet supported by Venafi Control Plane. +- The `ipAllowed`, `emailAllowed`, `uriAllowed`, and `upnAllowed` policy (`subjectAltNames`) do not apply as those SAN +types are not yet supported by Venafi Control Plane. +- If undefined key/value pairs are included in the policy specification, they will be silently ignored by this action. +This would include keys that are misspelled. ## Parameters for Viewing Certificate Policy API key: @@ -249,73 +263,95 @@ vcert getpolicy -k -z [--fil ``` Access token: ``` -vcert getpolicy -p TLSPC -t -z [--file ] +vcert getpolicy -p vcp -t -z [--file ] ``` Options: -|         Command         | Description | -|---------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| -| `--file` | Use to write the retrieved certificate policy to a file in JSON format. If not specified, policy is written to STDOUT. | -| `--starter` | Use to generate a template policy specification to help with getting started. `-k` and `-z` are ignored with this option. | +| Command | Description | +|-------------|----------------------------------------------------------------------------------------------------------------------------| +| `--file` | Use to write the retrieved certificate policy to a file in JSON format. If not specified, policy is written to STDOUT. | +| `--starter` | Use to generate a template policy specification to help with getting started. `-k` and `-z` are ignored with this option. | ## Examples For the purposes of the following examples, assume the following: -- The Venafi as a Service REST API is accessible at [https://api.venafi.cloud](https://api.venafi.cloud/swagger-ui.html) -- A user has been registered and granted at least the _OP Resource Owner_ role and has an API key of "3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4". -- A CA Account and Issuing Template have been created and configured appropriately (organization, city, state, country, key length, allowed domains, etc.). -- An Application has been created with a name of _Storefront_ to which the user has been given access, and the Issuing Template has been assigned to the Application with an API Alias of _Public Trust_. +- The Venafi Control Plane REST API is accessible at [https://api.venafi.cloud](https://api.venafi.cloud/vaas) +or [https://api.eu.venafi.cloud](https://api.eu.venafi.cloud/vaas) +- A user has been registered and granted at least the `OP Resource Owner` role and has an API key. +- A CA Account and Issuing Template have been created and configured appropriately (organization, city, state, country, +key length, allowed domains, etc.). +- An Application has been created with a name of `Storefront` to which the user has been given access, and the Issuing +Template has been assigned to the Application with an API Alias of `Public Trust`. Use the help to view the command line syntax for enroll: ``` vcert enroll -h ``` -Submit a request to Venafi as a Service for enrolling a certificate with a common name of “first-time.venafi.example” using an authentication token and have VCert prompt for the password to encrypt the private key: + +Submit a request to Venafi Control Plane for enrolling a certificate with a common name of `first-time.venafi.example` +using an api key and have VCert prompt for the password to encrypt the private key: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --cn first-time.venafi.example ``` -Submit a request to Venafi as a Service for enrolling a certificate where the password for encrypting the private key to be generated is specified in a text file called passwd.txt: + +Submit a request to Venafi Control Plane for enrolling a certificate where the password for encrypting the private key +to be generated is specified in a text file called passwd.txt: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --key-password file:passwd.txt --cn passwd-from-file.venafi.example ``` -Submit a request to Venafi as a Service for enrolling a certificate where the private key to be generated is not password encrypted: + +Submit a request to Venafi Control Plane for enrolling a certificate where the private key to be generated is not +password encrypted: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --cn non-encrypted-key.venafi.example --no-prompt ``` -Submit a request to Venafi as a Service for enrolling a certificate using an externally generated CSR: + +Submit a request to Venafi Control Plane for enrolling a certificate using an externally generated CSR: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --csr file:/opt/pki/cert.req ``` -Submit a request to Venafi as a Service for enrolling a certificate where the certificate and private key are output using JSON syntax to a file called json.txt: + +Submit a request to Venafi Control Plane for enrolling a certificate where the certificate and private key are output +using JSON syntax to a file called json.txt: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --key-password Passw0rd --cn json-to-file.venafi.example --format json --file keycert.json ``` -Submit a request to Venafi as a Service for enrolling a certificate where only the certificate and private key are output, no chain certificates: + +Submit a request to Venafi Control Plane for enrolling a certificate where only the certificate and private key are +output, no chain certificates: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --key-password Passw0rd --cn no-chain.venafi.example --chain ignore ``` -Submit a request to Venafi as a Service for enrolling a certificate with three DNS subject alternative names: + +Submit a request to Venafi Control Plane for enrolling a certificate with three DNS subject alternative names: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --no-prompt --cn three-sans.venafi.example --san-dns first-san.venafi.example --san-dns second-san.venafi.example --san-dns third-san.venafi.example ``` -Submit request to Venafi as a Service for enrolling a certificate where the certificate is not issued after two minutes and then subsequently retrieve that certificate after it has been issued: + +Submit request to Venafi Control Plane for enrolling a certificate where the certificate is not issued after two +minutes and then subsequently retrieve that certificate after it has been issued: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --no-prompt --cn demo-pickup.venafi.example vcert pickup -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 --pickup-id "{7428fac3-d0e8-4679-9f48-d9e867a326ca}" ``` -Submit request to Venafi as a Service for enrolling a certificate that will be retrieved later using a Pickup ID from in a text file: + +Submit request to Venafi Control Plane for enrolling a certificate that will be retrieved later using a Pickup ID from +a text file: ``` vcert enroll -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 -z "Storefront\\Public Trust" --no-prompt --cn demo-pickup.venafi.example --no-pickup -pickup-id-file pickup_id.txt vcert pickup -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 --pickup-id-file pickup_id.txt ``` -Submit request to Venafi as a Service for renewing a certificate using the enrollment (pickup) ID of the expiring certificate: + +Submit request to Venafi Control Plane for renewing a certificate using the enrollment (pickup) ID of the expiring +certificate: ``` vcert renew -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 --id "{7428fac3-d0e8-4679-9f48-d9e867a326ca}" ``` -Submit request to Venafi as a Service for renewing a certificate using the expiring certificate file: + +Submit request to Venafi Control Plane for renewing a certificate using the expiring certificate file: ``` vcert renew -k 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 --thumbprint file:/opt/pki/demo.crt ``` @@ -328,23 +364,23 @@ vcert getcred --email ``` Options: -|         Command         | Description | -|---------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--email` | Use to specify a user's business email address. An email will be sent to this address with a link to activate the API key that is output by this action. This is required for (re)registerting with Venafi as a Service. | -| `--format` | Specify "json" to get more verbose JSON formatted output instead of the plain text default. | -| `--password` | Use to specify the user's password if it is expected the user will need to login to the [Venafi as a Service web UI](https://ui.venafi.cloud/). | +| Command | Description | +|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--email` | Use to specify a user's business email address. An email will be sent to this address with a link to activate the API key that is output by this action. This is required for (re)registering with Venafi Control Plane. | +| `--format` | Specify "json" to get more verbose JSON formatted output instead of the plain text default. | +| `--password` | Use to specify the user's password if it is expected the user will need to login to the [Venafi Control Plane web UI](https://ui.venafi.cloud/). | ### Obtaining an access token from service account ``` -vcert getcred --platform TLSPC --tenant-id 3dfcc6dc-7309-4dcf-aa7c-5d7a2ee368b4 --external-jwt "file:jwt.txt" +vcert getcred -p vcp --token-url https://api.venafi.cloud/v1/oauth2/v2.0/xxx-XXxX/token --idp-jwt "file:jwt.txt" ``` Options: -| Flag | Description | -|----------------------|-------------------------------------------------------------------------------------------------------------------| -| `-p` or `--platform` | Use to specify Venafi as a Service as the platform of choice to connect. Accepted values are: `TLSPC` and `VAAS`. | -| `--tenant-id` | The ID of the tenant (company) that VCert is connecting to | -| `--external-jwt` | The JWT of the Identity Provider associated to the service account that is going to grant the access token | +| Flag | Description | +|----------------------|-----------------------------------------------------------------------------------------------------------------------| +| `-p` or `--platform` | Use to specify Venafi Control Plane as the platform of choice to connect. Accepted value is `vcp`, no case-sensitive. | +| `--token-url` | The URL used to obtain the access token, provided by Venafi Control Plane's service account page | +| `--idp-jwt` | The JWT of the Identity Provider associated to the service account that is going to grant the access token | ### Generating a new key pair and CSR ``` diff --git a/README-PLAYBOOK.md b/README-PLAYBOOK.md index 58b00266..0897c7f8 100644 --- a/README-PLAYBOOK.md +++ b/README-PLAYBOOK.md @@ -1,26 +1,43 @@ # VCert Playbook -VCert Playbook functionality solves the "last mile" problem. VCert has historically done a great job fetching certificates, but getting those certificates installed in the right location usually required custom scripting. The Playbook functionality addresses this use case natively. +VCert Playbook functionality solves the "last mile" problem. VCert has historically done a great job fetching +certificates, but getting those certificates installed in the right location usually required custom scripting. The +Playbook functionality addresses this use case natively. -> Throughout this article, we use _TLS Protect Datacenter (TLSPDC)_ to refer to Trust Protection Platform and _TLS Protect Cloud (TLSPC)_ to refer to Venafi as a Service. +> Throughout this article, we use _TLS Protect Datacenter (TLSPDC)_ to refer to Trust Protection Platform and +> _TLS Protect Cloud (TLSPC)_ to refer to Venafi Control Plane. ## Key features of VCert playbook -- **Simplified commands**: With VCert Playbook, you can avoid long command-line arguments. Instead, use playbook YAML files with VCert to enhance automation and maintenance ease. +- **Simplified commands**: With VCert Playbook, you can avoid long command-line arguments. Instead, use playbook YAML +files with VCert to enhance automation and maintenance ease. -- **Flexible certificate placement**: You can designate where to place the certificate and the format in which you want it once received from Venafi. VCert Playbook supports common keystore formats like PEM, JKS, PKCS#12, accommodating folder locations and the Windows CAPI store. +- **Flexible certificate placement**: You can designate where to place the certificate and the format in which you +want it once received from Venafi. VCert Playbook supports common keystore formats like PEM, JKS, PKCS#12, accommodating +folder locations and the Windows CAPI store. -- **Post-installation actions**: Specify any actions that must be carried out after the certificate is installed. This includes restarting services like Apache or Nginx, or running any other scripts needed once the certificate is in the right location. +- **Post-installation actions**: Specify any actions that must be carried out after the certificate is installed. This +includes restarting services like Apache or Nginx, or running any other scripts needed once the certificate is in the +right location. -- **Smart certificate renewals**: VCert Playbook checks if a certificate already exists and whether it's due for renewal before requesting a new one. This functionality lets you run a script regularly without unnecessarily renewing certificates. +- **Smart certificate renewals**: VCert Playbook checks if a certificate already exists and whether it's due for +renewal before requesting a new one. This functionality lets you run a script regularly without unnecessarily renewing +certificates. -- **Compatibility**: VCert Playbook works seamlessly with the three Venafi platforms: TLS Protect Cloud, TLS Protect Datacenter, and Firefly, ensuring it fits your particular environment. +- **Compatibility**: VCert Playbook works seamlessly with the three Venafi platforms: TLS Protect Cloud, TLS Protect +Datacenter, and Firefly, ensuring it fits your particular environment. ## Example use cases -- **Automated certificate renewal**: You can set renewal parameters to have VCert automatically renew certificates before expiration. This approach assumes that VCert is part of a daily cronjob or is executed routinely through other automation methods. By default, renewal occurs at 10% of the remaining certificate lifetime. +- **Automated certificate renewal**: You can set renewal parameters to have VCert automatically renew certificates +before expiration. This approach assumes that VCert is part of a daily cronjob or is executed routinely through other +automation methods. By default, renewal occurs at 10% of the remaining certificate lifetime. -- **Effortless API access updates**: When using TLS Protect Datacenter, VCert will automatically update API access and refresh tokens within the playbook. This feature ensures continuous operation without manual intervention. It leverages a refresh token to acquire a new access token when needed, an approach that's particularly effective when paired with a long-lasting refresh/grant token and a short-lived access token, such as a 3-year refresh token and a 1-hour access token. +- **Effortless API access updates**: When using TLS Protect Datacenter, VCert will automatically update API access and +refresh tokens within the playbook. This feature ensures continuous operation without manual intervention. It leverages +a refresh token to acquire a new access token when needed, an approach that's particularly effective when paired with a +long-lasting refresh/grant token and a short-lived access token, such as a 3-year refresh token and a 1-hour access +token. ## Getting started VCert Playbook functionality is invoked using the `vcert run` command. @@ -32,23 +49,25 @@ VCert Playbook functionality is invoked using the `vcert run` command. ```sh vcert run -f path/to/my/playbook.yaml ``` -3. Setup a cronjob (or Windows scheduled task) to execute the playbook on a regular basis (usually daily) - Sample cronjob entry: +3. Set up a cronjob (or Windows scheduled task) to execute the playbook on a regular basis (usually daily). Sample +cronjob entry: ``` 0 23 * * * /usr/bin/sudo /usr/local/bin/vcert run -f ~/playbook.yaml >> /var/log/vcert-playbook.log 2>&1 ``` -> **Recommended**: For a detailed walkthrough for automating certificate lifecycle management using a VCert Playbook for NGINX, check out the guide on [Dev Central](https://developer.venafi.com/tlsprotectcloud/docs/vcert-auto-cert-mgt-using-tlspc)! +> **Recommended**: For a detailed walkthrough for automating certificate lifecycle management using a VCert Playbook +for NGINX, check out the guide on [Dev Central](https://developer.venafi.com/tlsprotectcloud/docs/vcert-auto-cert-mgt-using-tlspc)! ## Usage + VCert run playbook functionality is invoked using the `vcert run` command with additional arguments: ```sh vcert run [OPTIONAL ARGUMENTS] ``` For example, the following command will execute the playbook in ./path/to/my/playbook.yaml with debug output enabled: - ```sh vcert run --file path/to/my/playbook.yaml --debug ``` + ### VCert playbook arguments The following arguments are available with the `vcert run` command: @@ -71,7 +90,8 @@ Several playbook samples are provided in the [examples folder](./examples/playbo * [Playbook for Firefly using user/password authorization](./examples/playbook/sample.firefly.user-password.yaml) ## Playbook file structure and options -The playbook file is a YAML file that provides access information to either TLS Protect Cloud or TLS Protect Datacenter, defines the details of the certificate to request, and specifies the locations where the certificate should be installed. +The playbook file is a YAML file that provides access information to either TLS Protect Cloud or TLS Protect Datacenter, +defines the details of the certificate to request, and specifies the locations where the certificate should be installed. The top-level structure of the file is described as follows: @@ -181,13 +201,13 @@ The top-level structure of the file is described as follows: ### Location -| Field | Type | Required | Description | -|------------|---------|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------| -| instance | string | ***Required*** | Specifies the name of the installed node (typically the hostname). | -| replace | boolean | *Optional* | Replace the current object with new information. Defaults to `false`. | -| tlsAddress | string | ***Required*** | Specifies the IP address or hostname and port where the certificate can be validated by the Venafi Platform.
Example: `192.168.100.23:443`. | -| workload | string | *Optional* | Use to provide an identifier for the workload using the certificate. Example: `workload`. | -| zone | string | *Optional* | Use to provide a different policy folder for the device object to be created in, when platform is TPP. If excluded, the device object is created in the same policy folder as the certificate. Example: `Installations\Agentless\Datacenters\PHX`| +| Field | Type | Required | Description | +|------------|---------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| instance | string | ***Required*** | Specifies the name of the installed node (typically the hostname). | +| replace | boolean | *Optional* | Replace the current object with new information. Defaults to `false`. | +| tlsAddress | string | ***Required*** | Specifies the IP address or hostname and port where the certificate can be validated by the Venafi Platform.
Example: `192.168.100.23:443`. | +| workload | string | *Optional* | Use to provide an identifier for the workload using the certificate. Example: `workload`. | +| zone | string | *Optional* | Use to provide a different policy folder for the device object to be created in, when platform is TPP. If excluded, the device object is created in the same policy folder as the certificate. Example: `Installations\Agentless\Datacenters\PHX` | ### Subject diff --git a/README-POLICY-SPEC.md b/README-POLICY-SPEC.md index 5a952b6c..7831cf6a 100644 --- a/README-POLICY-SPEC.md +++ b/README-POLICY-SPEC.md @@ -1,22 +1,18 @@ # Venafi Certificate and Key Policy Specification -The _Venafi Certificate and Key Policy Specification_ is a standard for defining -constraints and recommendations that govern key generation and certificate issuance. -The specification is consumable by the VCert CLI and VCert-based integrations like -the [Venafi Collection for Ansible](https://github.com/Venafi/ansible-collection-venafi) -and the [Venafi Provider for HashiCorp Terraform](https://github.com/Venafi/terraform-provider-venafi) -that support _Certificate Policy Management_ for Trust Protection Platform (TPP) and Venafi -as a Service (VaaS). +The _Venafi Certificate and Key Policy Specification_ is a standard for defining constraints and recommendations that +govern key generation and certificate issuance. The specification is consumable by the VCert CLI and VCert-based +integrations like the [Venafi Collection for Ansible](https://github.com/Venafi/ansible-collection-venafi) and the +[Venafi Provider for HashiCorp Terraform](https://github.com/Venafi/terraform-provider-venafi) that support _Certificate +Policy Management_ for Trust Protection Platform (TPP) and Venafi Control Plane (VCP). ## Policy-as-Code Structure (JSON) -The structure of the _Venafi Certificate and Key Policy Specification_ is shown -below and is the same starter policy that can be output by executing the `vcert -getpolicy --starter` command. The specification has two sections, "policy" and -"defaults". The "policy" section specifies values with which new certificate requests -must comply and the "defaults" section specifies values that are recommended for use -in certificate requests when those values are not specified or overridden. -VCert also supports YAML formatted input specifications. +The structure of the _Venafi Certificate and Key Policy Specification_ is shown below and is the same starter policy +that can be output by executing the `vcert getpolicy --starter` command. The specification has two sections, `policy` +and `defaults`. The `policy` section specifies values with which new certificate requests must comply and the `defaults` +section specifies values that are recommended for use in certificate requests when those values are not specified or +overridden. VCert also supports YAML formatted input specifications. ```json { @@ -67,47 +63,86 @@ VCert also supports YAML formatted input specifications. } ``` -## Policy-as-Code Parameters - -All parameters in a specification are optional thus `{}` is the most simple valid -specification and results in a policy that uses TPP or VaaS defaults. - -| Parameter | Data Type | Description | -| ---- | ---- | ---- | -| `policy`                                   ||| -|  `domains` |string array| Specifies domain suffixes that are permitted in Common Name (CN) and DNS Subject Alternative Name (SAN) values | -|  `wildcardAllowed` |boolean| Indicates whether CN and DNS SAN values may specify wildcards like "*.example.com" | -|  `autoInstalled` |boolean| ![TPP Only](https://img.shields.io/badge/TPP%20Only-orange.svg) Indicates whether the requested certificate will be automatically installed (i.e. provisioned) | -|  `maxValidDays` |integer| Number of days for which the requested certificate will be valid. May be ignored if the integration with the issuing CA does not support specific end dates. | -|  `certificateAuthority` |string| **TPP**: the distinguished name of a CA Template object.
For example, "\VED\Policy\Certificate Authorites\Entrust Advantage"

**VaaS**: CA Account Type ("DIGICERT", "ENTRUST", "GLOBALSIGN", or "BUILTIN"), CA Account Name (as it appears in the web console), and CA Product Type delimited by backslash characters.
For example, "DIGICERT\My DigiCert Account\ssl_plus" | -|  `subject` ||| -|   `orgs` | string array | Organization (O) values that are permitted | -|   `orgUnits` | string array | Organizational Unit (OU) values that are permitted | -|   `localities` | string array | City/Locality (L) values that are permitted | -|   `states` | string array | State/Province (ST) values that are permitted | -|   `countries` | string array | [ISO 3166 2-Alpha](https://www.iso.org/obp/ui/#search/code/) Country (C) code values that are permitted | -|   `keyPair` ||| -|   `keyTypes` | string array | Key algorithm: "RSA" and/or "ECDSA" | -|   `rsaKeySizes` | integer array | Permitted number of bits for RSA keys: 512, 1024, 2048, 3072, and/or 4096 | -|   `ellipticCurves` | string array | Permitted elliptic curves: "P256", "P384", and/or "P521" | -|   `serviceGenerated` | boolean | Indicates whether key pair and CSR must be generated by the Venafi machine identity service | -|   `reuseAllowed` | boolean | Indicates whether new certificate requests are permitted to reuse a key pair of a known certificate | -|  `subjectAltNames` ||| -|   `dnsAllowed` | boolean | Indicates whether DNS Subject Alternative Names are permitted| -|   `ipAllowed` | boolean | Indicates whether IP Address Subject Alternative Names are permitted | -|   `emailAllowed` | boolean | Indicates whether Email Address (RFC822) Subject Alternative Names are permitted | -|   `uriAllowed` | boolean | Indicates whether Uniform Resource Indicator (URI) Subject Alternative Names are permitted | -|   `upnAllowed` | boolean | ![TPP Only](https://img.shields.io/badge/TPP%20Only-orange.svg) Indicates whether User Principal Name (UPN) Subject Alternative Names are permitted | -| `defaults` ||| -|  `domain` |string| Domain suffix that should be used by default (e.g. "example.com")| -|  `subject` ||| -|   `org` | string | Organization (O) value that should be used by default (e.g. "Example, Inc.")| -|   `orgUnits` | string array | Organizational Unit (OU) values that should be used by default (e.g. "Quality Assurance")| -|   `locality` | string | City/Locality (L) value that should be used by default (e.g. "Salt Lake City")| -|   `state` | string | State/Province (ST) value that should be used by default (e.g. "Utah")| -|   `country` | string |[ISO 3166 2-Alpha](https://www.iso.org/obp/ui/#search/code/) Country (C) code value that should be used by default (e.g. "US")| -|  `keyPair` ||| -|   `keyType` | string | Key algorithm that should be used by default, "RSA" or "ECDSA"| -|   `rsaKeySize` | integer | Number of bits that should be used by default for RSA keys: 512, 1024, 2048, 3072, or 4096| -|   `ellipticCurve` | string | The elliptic curve that should be used by default: "P256", "P384", "P521"
or _"ED25519"_ ![VaaS Only](https://img.shields.io/badge/VaaS%20Only-orange.svg)| -|   `serviceGenerated` | boolean | Indicates whether keys should be generated by the Venafi machine identity service by default| +## Policy-as-Code structure + +All parameters in a specification are optional thus `{}` is the simplest valid specification and results in a policy +that uses TPP or VCP defaults. + +### Policy Specification + +| Parameter | Description | Data Type | +|------------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| `defaults` | [Defaults](#defaults) object | Default values used when Certificate Signing Request does not specify one. Must be a subset of values defined in [Policy](#policy) | +| `policy` | [Policy](#policy) object | Values allowed for certificates requested using this policy | + +### Defaults + +| Parameter | Description | Data Type | +|-----------|------------------------------------------|--------------------------------------------------------------------------------------| +| `domain` | string | Domain suffix that should be used by default (e.g. "example.com") | +| `keyPair` | [DefaultKeyPair](#defaultkeypair) object | The private key type, size and length that should be used by default (e.g. RSA 2048) | +| `subject` | [DefaultSubject](#defaultsubject) object | The `subject` values that should be used by default | + + +### DefaultKeyPair + +| Parameter | Description | Data Type | +|--------------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ellipticCurve` | string | The elliptic curve that should be used by default: "P256", "P384", "P521"
or _"ED25519"_ ![VCP Only](https://img.shields.io/badge/VCP-3d3dcc.svg) | +| `keyType` | string | Key algorithm that should be used by default, "RSA" or "ECDSA" | +| `rsaKeySize` | integer | Number of bits that should be used by default for RSA keys: 512, 1024, 2048, 3072, or 4096 | +| `serviceGenerated` | boolean | Indicates whether keys should be generated by the Venafi machine identity service by default | + +### DefaultSubject + +| Parameter | Description | Data Type | +|------------|-----------------|--------------------------------------------------------------------------------------------------------------------------------| +| `country` | string | [ISO 3166 2-Alpha](https://www.iso.org/obp/ui/#search/code/) Country (C) code value that should be used by default (e.g. "US") | +| `locality` | string | City/Locality (L) value that should be used by default (e.g. "Salt Lake City") | +| `org` | string | Organization (O) value that should be used by default (e.g. "Example, Inc.") | +| `orgUnits` | array of string | Organizational Unit (OU) values that should be used by default (e.g. "Quality Assurance") | +| `state` | string | State/Province (ST) value that should be used by default (e.g. "Utah") | + +### Policy + +| Parameter | Data Type | Description | +|------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `autoInstalled` | boolean | ![TPP Only](https://img.shields.io/badge/TPP%20Only-orange.svg) Indicates whether the requested certificate will be automatically installed (i.e. provisioned) | +| `certificateAuthority` | string | ![TPP](https://img.shields.io/badge/TPP-orange.svg) The distinguished name of a CA Template object.
For example, "\VED\Policy\Certificate Authorites\Entrust Advantage"

![VCP](https://img.shields.io/badge/VCP-3d3dcc.svg) CA Account Type ("DIGICERT", "ENTRUST", "GLOBALSIGN", or "BUILTIN"), CA Account Name (as it appears in the web console), and CA Product Type delimited by backslash characters.
For example, "DIGICERT\My DigiCert Account\ssl_plus" | +| `domains` | array of string | Specifies domain suffixes that are permitted in Common Name (CN) and DNS Subject Alternative Name (SAN) values | +| `keyPair` | [KeyPair](#keypair) object | The private key settings allowed for certificates requested using this policy | +| `maxValidDays` | integer | Number of days for which the requested certificate will be valid. May be ignored if the integration with the issuing CA does not support specific end dates. | +| `subject` | [Subject](#subject) object | The `subject` values allowed for certificates requested using this policy | +| `subjectAltNames` | [SubjectAltNames](#subjectaltnames) object | The SANs values allowed for certificates requested using this policy | +| `wildcardAllowed` | boolean | Indicates whether CN and DNS SAN values may specify wildcards like "*.example.com" | + +### KeyPair + +| Parameter | Data Type | Description | +|--------------------|------------------|-----------------------------------------------------------------------------------------------------| +| `ellipticCurves` | array of string | Permitted elliptic curves: `P256`, `P384`, and/or `P521` | +| `keyTypes` | array of string | Key algorithm: `RSA` and/or `ECDSA` | +| `reuseAllowed` | boolean | Indicates whether new certificate requests are permitted to reuse a key pair of a known certificate | +| `rsaKeySizes` | array of integer | Permitted number of bits for RSA keys: `512`, `1024`, `2048`, `3072`, and/or `4096` | +| `serviceGenerated` | boolean | Indicates whether key pair and CSR must be generated by the Venafi machine identity service | + + +### Subject + +| Parameter | Data Type | Description | +|--------------|-----------------|---------------------------------------------------------------------------------------------------------| +| `countries` | array of string | [ISO 3166 2-Alpha](https://www.iso.org/obp/ui/#search/code/) Country (C) code values that are permitted | +| `localities` | array of string | City/Locality (L) values that are permitted | +| `orgs` | array of string | Organization (O) values that are permitted | +| `orgUnits` | array of string | Organizational Unit (OU) values that are permitted | +| `states` | array of string | State/Province (ST) values that are permitted | + +### SubjectAltNames + +| Parameter | Data Type | Description | +|----------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| `dnsAllowed` | boolean | Indicates whether DNS Subject Alternative Names are permitted | +| `ipAllowed` | boolean | Indicates whether IP Address Subject Alternative Names are permitted | +| `emailAllowed` | boolean | Indicates whether Email Address (RFC822) Subject Alternative Names are permitted | +| `uriAllowed` | boolean | Indicates whether Uniform Resource Indicator (URI) Subject Alternative Names are permitted | +| `upnAllowed` | boolean | ![TPP Only](https://img.shields.io/badge/TPP%20Only-orange.svg) Indicates whether User Principal Name (UPN) Subject Alternative Names are permitted | diff --git a/README.md b/README.md index 9c67fe25..42e2962f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Venafi](https://raw.githubusercontent.com/Venafi/.github/master/images/Venafi_logo.png)](https://www.venafi.com/) [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![Community Supported](https://img.shields.io/badge/Support%20Level-Community-brightgreen) -![Compatible with TPP 17.3+ & VaaS & Firefly](https://img.shields.io/badge/Compatibility-TPP%2017.3+%20%26%20VaaS%20%26%20Firefly-f9a90c) +![Compatible with TPP 17.3+ & VCP & Firefly](https://img.shields.io/badge/Compatibility-TPP%2017.3+%20%26%20VCP%20%26%20Firefly-f9a90c) _**This open source project is community-supported.** To report a problem or share an idea, use **[Issues](../../issues)**; and if you have a suggestion for fixing the issue, please include those details, too. In addition, use **[Pull Requests](../../pulls)** to contribute actual bug fixes or proposed enhancements. @@ -10,19 +10,22 @@ We welcome and appreciate all contributions. Got questions or want to discuss so # VCert -[![GoDoc](https://godoc.org/github.com/Venafi/vcert?status.svg)](https://pkg.go.dev/github.com/Venafi/vcert) [![Go Report Card](https://goreportcard.com/badge/github.com/Venafi/vcert)](https://goreportcard.com/report/github.com/Venafi/vcert) +[![GoDoc](https://godoc.org/github.com/Venafi/vcert?status.svg)](https://pkg.go.dev/github.com/Venafi/vcert) +[![Go Report Card](https://goreportcard.com/badge/github.com/Venafi/vcert)](https://goreportcard.com/report/github.com/Venafi/vcert) [![Used By](https://sourcegraph.com/github.com/Venafi/vcert/-/badge.svg)](https://sourcegraph.com/github.com/Venafi/vcert?badge) VCert is a Go library, SDK, and command line utility designed to simplify key generation and enrollment of machine identities (also known as SSL/TLS certificates and keys) that comply with enterprise security policy by using the -[Venafi Trust Protection Platform](https://www.venafi.com/platform/trust-protection-platform) or [Venafi as a Service](https://www.venafi.com/venaficloud) or [Venafi Firefly](https://venafi.com/firefly/). +[Venafi Trust Protection Platform](https://www.venafi.com/platform/trust-protection-platform) +or [Venafi Control Plane](https://www.venafi.com/venaficloud) or [Venafi Firefly](https://venafi.com/firefly/). See [VCert CLI for Venafi Trust Protection Platform](README-CLI-PLATFORM.md) or -[VCert CLI for Venafi as a Service](README-CLI-CLOUD.md) or [VCert CLI for Venafi Firefly](README-CLI-FIREFLY.md) to get started with the command line utility. +[VCert CLI for Venafi Control Plane](README-CLI-CLOUD.md) or [VCert CLI for Venafi Firefly](README-CLI-FIREFLY.md) +to get started with the command line utility. #### Compatibility -VCert releases are tested using the latest version of Trust Protection Platform. General functionality of the +VCert releases are tested using the latest version of Trust Protection Platform. General functionality of the [latest VCert release](../../releases/latest) should be compatible with Trust Protection Platform 17.3 or higher. Custom Fields and Instance Tracking require TPP 18.2 or higher, and Token Authentication requires TPP 20.1 or higher. @@ -31,50 +34,56 @@ Custom Fields and Instance Tracking require TPP 18.2 or higher, and Token Authen 1. Configure your Go environment according to https://golang.org/doc/install. 2. Verify that GOPATH environment variable is set correctly 3. Download the source code: - -```sh -go get github.com/Venafi/vcert/v5 -``` - -or - -Pre Go 1.13 -```sh -git clone https://github.com/Venafi/vcert.git $GOPATH/src/github.com/Venafi/vcert/v5 -``` - -Go 1.11 with go modules enabled or go 1.13 and up make sure to clone outside of `$GOPATH/src` -```sh -git clone https://github.com/Venafi/vcert.git -``` - -4. Build the command line utilities for Linux, MacOS, and Windows: - -```sh -make build -``` + ```sh + go get github.com/Venafi/vcert/v5 + ``` + + or pre Go 1.13 + + ```sh + git clone https://github.com/Venafi/vcert.git $GOPATH/src/github.com/Venafi/vcert/v5 + ``` + + Go 1.11 with go modules enabled or go 1.13 and up make sure to clone outside of `$GOPATH/src` + ```sh + git clone https://github.com/Venafi/vcert.git + ``` + +4. Build the command line utilities for Linux, macOS, and Windows: + ```sh + make build + ``` ## Using VCert to integrate Venafi with your application For code samples of programmatic use, please review the files in [examples folder](./examples). ### Common part -1. In your main.go file, make the following import declarations: `github.com/Venafi/vcert/v5`, `github.com/Venafi/vcert/v5/pkg/certificate`, and `github.com/Venafi/vcert/v5/pkg/endpoint`. -2. Create a configuration object of type `&vcert.Config` that specifies the Venafi connection details. Solutions are typically designed to get those details from a secrets vault, .ini file, environment variables, or command line parameters. +1. In your `main.go` file, make the following import declarations: + ```golang + import ( + "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/endpoint" + ) + ``` +2. Create a configuration object of type `&vcert.Config` that specifies the Venafi connection details. Solutions are +typically designed to get those details from a secrets vault, .ini file, environment variables, or command line parameters. ### Enroll certificate 1. Instantiate a client by calling the `NewClient` method of the vcert class with the configuration object. -1. Compose a certificate request object of type `&certificate.Request`. -1. Generate a key pair and CSR for the certificate request by calling the `GenerateRequest` method of the client. -1. Submit the request by passing the certificate request object to the `RequestCertificate` method of the client. -1. Use the request ID to pickup the certificate using the `RetrieveCertificate` method of the client. +2. Compose a certificate request object of type `&certificate.Request`. +3. Generate a key pair and CSR for the certificate request by calling the `GenerateRequest` method of the client. +4. Submit the request by passing the certificate request object to the `RequestCertificate` method of the client. +5. Use the request ID to pickup the certificate using the `RetrieveCertificate` method of the client. ### New TLS listener for domain -1. Call `vcert.Config` method `NewListener` with list of domains as arguments. For example `("test.example.com:8443", "example.com")` +1. Call `vcert.Config` method `NewListener` with list of domains as arguments. +For example `("test.example.com:8443", "example.com")` 2. Use gotten `net.Listener` as argument to built-in `http.Serve` or other https servers. -Samples are in a state where you can build/execute them using the following commands (after setting the environment variables discussed later): - +Samples are in a state where you can build/execute them using the following commands (after setting the environment +variables discussed later): ```sh go build -o cli ./example go test -v ./example -run TestRequestCertificate @@ -83,7 +92,8 @@ go test -v ./example -run TestRequestCertificate ## Prerequisites for using with Trust Protection Platform 1. A user account that has been granted WebSDK Access -2. A folder (zone) where the user has been granted the following permissions: View, Read, Write, Create, Revoke (for the revoke action), and Private Key Read (for the pickup action when CSR is service generated) +2. A folder (zone) where the user has been granted the following permissions: `View`, `Read`, `Write`, `Create`, +`Revoke` (for the revoke action), and `Private Key Read` (for the pickup action when CSR is service generated) 3. Policy applied to the folder which specifies: 1. CA Template that Trust Protection Platform will use to enroll certificate requests submitted by VCert 2. Subject DN values for Organizational Unit (OU), Organization (O), City (L), State (ST) and Country (C) @@ -94,18 +104,20 @@ go test -v ./example -run TestRequestCertificate 7. (Recommended) Key Bit Strength set to 2048 or higher 8. (Recommended) Domain Whitelisting policy appropriately assigned -The requirement for the CA Template to be assigned by policy follows a long standing Venafi best practice which also met our design objective to keep the certificate request process simple for VCert users. If you require the ability to specify the CA Template with the request you can use the TPP REST APIs but please be advised this goes against Venafi recommendations. +The requirement for the CA Template to be assigned by policy follows a long-standing Venafi best practice +which also met our design objective to keep the certificate request process simple for VCert users. +If you require the ability to specify the CA Template with the request you can use the TPP REST APIs +but please be advised this goes against Venafi recommendations. -## Testing with Trust Protection Platform and Venafi as a Service +## Testing with Trust Protection Platform and Venafi Control Plane Unit tests: - ```sh make test ``` -Integration tests for Trust Protection Platform and Venafi as a Service require access to those products. Environment -variables are used to specify required settings including credentials. The VaaS API key and zone value +Integration tests for Trust Protection Platform and Venafi Control Plane require access to those products. Environment +variables are used to specify required settings including credentials. The Venafi Control Plane API key and zone value fragments (i.e. `Application Name`\\`Issuing Template API Alias`) are readily available in the web interface. ```sh @@ -131,19 +143,25 @@ make cloud_test Command line utility tests make use of [Cucumber & Aruba](https://github.com/cucumber/aruba) feature files. - To run tests for all features in parallel: - -```sh -make cucumber -``` - -- To run tests only for a specific feature (e.g. basic, config, enroll, format, gencsr, renew, or revoke): - -```sh -make cucumber FEATURE=./features/basic/version.feature -``` + ```sh + make cucumber + ``` + +- To run tests only for a specific feature: + ```sh + make cucumber FEATURE=./features/basic/version.feature + ``` + Available features are: + - `basic` + - `config` + - `enroll` + - `format` + - `gencsr` + - `renew` + - `revoke` When run, these tests will be executed in their own Docker container using the Ruby version of Cucumber. -The completed test run will report on the number of test "scenarios" and "steps" that passed, failed, or were skipped. +The completed test run will report on the number of test scenarios and steps that passed, failed, or were skipped. ## Playbook functionality @@ -154,17 +172,29 @@ For detailed explanations about the playbook and how it is build please check he Venafi welcomes contributions from the developer community. 1. Fork it to your account (https://github.com/Venafi/vcert/fork) -2. Clone your fork (`git clone git@github.com:youracct/vcert.git`) -3. Create a feature branch (`git checkout -b your-branch-name`) +2. Clone your fork: + ```sh + git clone git@github.com:youracct/vcert.git + ``` +3. Create a feature branch: + ```sh + git checkout -b your-branch-name + ``` 4. Implement and test your changes -5. Commit your changes (`git commit -am 'Added some cool functionality'`) -6. Push to the branch (`git push origin your-branch-name`) -7. Create a new Pull Request (https://github.com/youracct/vcert/pull/new/your-branch-name) +5. Commit your changes: + ```sh + git commit -am 'Added some cool functionality' + ``` +6. Push to the branch + ```sh + git push origin your-branch-name + ``` +7. Create a new Pull Request at https://github.com/youracct/vcert/pull/new/your-branch-name ## License Copyright © Venafi, Inc. All rights reserved. -VCert is licensed under the Apache License, Version 2.0. See `LICENSE` for the full license text. +VCert is licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for the full license text. Please direct questions/comments to opensource@venafi.com. diff --git a/cmd/vcert/certificates.go b/cmd/vcert/certificates.go new file mode 100644 index 00000000..045d1663 --- /dev/null +++ b/cmd/vcert/certificates.go @@ -0,0 +1,721 @@ +/* + * Copyright 2020-2024 Venafi, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/urfave/cli/v2" + + "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/util" +) + +var ( + commandEnroll = &cli.Command{ + Before: runBeforeCommand, + Flags: enrollFlags, + Action: doCommandEnroll1, + Name: commandEnrollName, + Usage: "To enroll a certificate", + UsageText: ` vcert enroll + + vcert enroll -k -z "\" --cn + vcert enroll -k -z "\" --cn --key-type rsa --key-size 4096 --san-dns --san-dns + vcert enroll -p vcp -t -z "\" --cn + + vcert enroll -u https://tpp.example.com -t -z "" --cn + vcert enroll -u https://tpp.example.com -t -z "" --cn --key-size 4096 --san-dns --san-dns + vcert enroll -u https://tpp.example.com -t -z "" --cn --key-type ecdsa --key-curve p384 --san-dns -san-dns + vcert enroll -u https://tpp.example.com -z "" --p12-file --p12-password --cn + vcert enroll -p tpp -u https://tpp.example.com -t -z "" --cn + + vcert enroll -p firefly -u -t -z "" --cn `, + } + + commandPickup = &cli.Command{ + Before: runBeforeCommand, + Name: commandPickupName, + Flags: pickupFlags, + Action: doCommandPickup1, + Usage: "To download a certificate", + UsageText: ` vcert pickup + + vcert pickup -k [--pickup-id | --pickup-id-file ] + vcert pickup -p vcp -t [--pickup-id | --pickup-id-file ] + + vcert pickup -u https://tpp.example.com -t --pickup-id + vcert pickup -p tpp -u https://tpp.example.com -t --pickup-id `, + } + + commandRevoke = &cli.Command{ + Before: runBeforeCommand, + Name: commandRevokeName, + Flags: revokeFlags, + Action: doCommandRevoke1, + Usage: "To revoke a certificate", + UsageText: ` vcert revoke + + vcert revoke -u https://tpp.example.com -t --thumbprint + vcert revoke -u https://tpp.example.com -t --id + vcert revoke -p tpp -u https://tpp.example.com -t --id `, + } + + commandRenew = &cli.Command{ + Before: runBeforeCommand, + Name: commandRenewName, + Flags: renewFlags, + Action: doCommandRenew1, + Usage: "To renew a certificate", + UsageText: ` vcert renew + + vcert renew -k --thumbprint + vcert renew -p vcp -t --thumbprint + + vcert renew -u https://tpp.example.com -t --id `, + } + + commandRetire = &cli.Command{ + Before: runBeforeCommand, + Name: commandRetireName, + Flags: retireFlags, + Action: doCommandRetire, + Usage: "To retire a certificate", + UsageText: ` vcert retire + + vcert retire -k --thumbprint + vcert retire -p vcp -t --thumbprint + + vcert retire -u https://tpp.example.com -t --id `, + } + + commandGenCSR = &cli.Command{ + Before: runBeforeCommand, + Name: commandGenCSRName, + Flags: genCsrFlags, + Action: doCommandGenCSR1, + Usage: "To generate a certificate signing request (CSR)", + UsageText: ` vcert gencsr --cn -o --ou -l --st -c --key-file --csr-file + vcert gencsr --cn -o --ou --ou -l --st -c --key-file --csr-file `, + } +) + +func doCommandEnroll1(c *cli.Context) error { + err := validateEnrollFlags(c.Command.Name) + if err != nil { + return err + } + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + var req = &certificate.Request{} + var pcc = &certificate.PEMCollection{} + + zoneConfig, err := connector.ReadZoneConfiguration() + + if err != nil { + return err + } + logf("Successfully read zone configuration for %s", flags.zone) + req = fillCertificateRequest(req, &flags) + err = connector.GenerateRequest(zoneConfig, req) + if err != nil { + return err + } + + var requestedFor string + if req.Subject.CommonName != "" { + requestedFor = req.Subject.CommonName + } else { + requestedFor = flags.csrOption + } + + logf("Successfully created request for %s", requestedFor) + passwordAutogenerated := false + + if connector.SupportSynchronousRequestCertificate() { + pcc, err = connector.SynchronousRequestCertificate(req) + if err != nil { + return err + } + logf("Successfully requested certificate for %s", requestedFor) + } else { + flags.pickupID, err = connector.RequestCertificate(req) + if err != nil { + return err + } + + logf("Successfully posted request for %s, will pick up by %s", requestedFor, flags.pickupID) + + if flags.noPickup { + pcc, err = certificate.NewPEMCollection(nil, req.PrivateKey, []byte(flags.keyPassword), flags.format) + if err != nil { + return err + } + } else { + req.PickupID = flags.pickupID + req.ChainOption = certificate.ChainOptionFromString(flags.chainOption) + req.KeyPassword = flags.keyPassword + + // Creates a temporary password for service generated csr if following validation is fulfilled. + // Analyzing validation assuming that pkcs12, legacy-pkcs12, jks and service flags are true + //+-------------+----------------+----------------------------------------------------------------------+ + //| --no-prompt | --key-password | What happens in validation? | + //|-------------|----------------|----------------------------------------------------------------------| + //| true | true |VCert will ignore prompt and create a certificate with given password | + //|-------------|----------------|----------------------------------------------------------------------| + //| false | true |VCert will ignore prompt and create a certificate with given password | + //|-------------|----------------|----------------------------------------------------------------------| + //| true | false |VCert will ignore prompt and create a certificate with NO password set| + //|-------------|----------------|----------------------------------------------------------------------| + //| false | false |VCert will prompt to enter password and process will not be completed | + //| | |until password is provided by user | + //+-------------+----------------+----------------------------------------------------------------------+ + if flags.noPrompt && flags.keyPassword == "" && flags.format != P12Format && flags.format != LegacyP12Format && flags.format != JKSFormat && flags.csrOption == "service" { + flags.keyPassword = fmt.Sprintf("t%d-%s.tem.pwd", time.Now().Unix(), randRunes(4)) + req.KeyPassword = flags.keyPassword + passwordAutogenerated = true + } + + req.Timeout = time.Duration(180) * time.Second + pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) + if err != nil { + return err + } + logf("Successfully retrieved request for %s", flags.pickupID) + + if req.CsrOrigin == certificate.LocalGeneratedCSR { + // otherwise private key can be taken from *req + err := pcc.AddPrivateKey(req.PrivateKey, []byte(flags.keyPassword), flags.format) + if err != nil { + log.Fatal(err) + } + } + } + } + + if (pcc.PrivateKey != "" && (flags.format == P12Format || flags.format == LegacyP12Format || flags.format == JKSFormat)) || (flags.format == util.LegacyPem && flags.csrOption == "service") || flags.noPrompt && passwordAutogenerated { + privKey, err := util.DecryptPkcs8PrivateKey(pcc.PrivateKey, flags.keyPassword) + if err != nil { + if err.Error() == "pkcs8: only PBES2 supported" && connector.GetType() == endpoint.ConnectorTypeTPP { + return fmt.Errorf("ERROR: To continue, you must select either the SHA1 3DES or SHA256 AES256 private key PBE algorithm. In a web browser, log in to TLS Protect and go to Configuration > Folders, select your zone, then click Certificate Policy and expand Show Advanced Options to make the change.") + } + return err + } + pcc.PrivateKey = privKey + } + + if flags.csrOption == "service" && flags.format == util.LegacyPem && !passwordAutogenerated { + pcc.PrivateKey, err = util.EncryptPkcs1PrivateKey(pcc.PrivateKey, flags.keyPassword) + if err != nil { + return nil + } + } + + // removing temporary password if it was set + if passwordAutogenerated { + flags.keyPassword = "" + } + result := &Result{ + Pcc: pcc, + PickupId: flags.pickupID, + Config: &Config{ + Command: c.Command.Name, + Format: flags.format, + JKSAlias: flags.jksAlias, + JKSPassword: flags.jksPassword, + ChainOption: certificate.ChainOptionFromString(flags.chainOption), + AllFile: flags.file, + KeyFile: flags.keyFile, + CertFile: flags.certFile, + ChainFile: flags.chainFile, + PickupIdFile: flags.pickupIDFile, + KeyPassword: flags.keyPassword, + }, + } + + err = result.Flush() + + if err != nil { + return fmt.Errorf("Failed to output the results: %s", err) + } + return nil +} + +func doCommandPickup1(c *cli.Context) error { + + isServiceGen := IsCSRServiceVaaSGenerated(c.Command.Name) + wasPasswordEmpty := false + if flags.noPrompt && flags.keyPassword == "" && flags.format != P12Format && flags.format != LegacyP12Format && flags.format != JKSFormat && (isServiceGen || isTppConnector(c.Command.Name)) { + flags.keyPassword = fmt.Sprintf("t%d-%s.tem.pwd", time.Now().Unix(), randRunes(4)) + wasPasswordEmpty = true + } + + err := validatePickupFlags1(c.Command.Name) + if err != nil { + return err + } + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + if flags.pickupIDFile != "" { + bytes, err := os.ReadFile(flags.pickupIDFile) + if err != nil { + return fmt.Errorf("Failed to read Pickup ID value: %s", err) + } + flags.pickupID = strings.TrimSpace(string(bytes)) + } + var req = &certificate.Request{ + PickupID: flags.pickupID, + ChainOption: certificate.ChainOptionFromString(flags.chainOption), + } + if flags.keyPassword != "" { + // key password is provided, which means will be requesting private key + req.KeyPassword = flags.keyPassword + req.FetchPrivateKey = true + } + var pcc *certificate.PEMCollection + pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) + if err != nil { + errStr := err.Error() + sliceString := strings.Split(errStr, ":") + size := len(sliceString) + errToValidate := sliceString[size-1] + if strings.TrimSpace(errToValidate) == "Failed to lookup private key vault id" && wasPasswordEmpty { + req.KeyPassword = "" + req.FetchPrivateKey = false + pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) + + if err != nil { + return fmt.Errorf("Failed to retrieve certificate: %s", err) + } + + } else { + return fmt.Errorf("Failed to retrieve certificate: %s", err) + } + } + logf("Successfully retrieved request for %s", flags.pickupID) + + if pcc.PrivateKey != "" && (flags.format == P12Format || flags.format == LegacyP12Format || flags.format == JKSFormat || flags.format == util.LegacyPem) || (flags.noPrompt && wasPasswordEmpty && pcc.PrivateKey != "") { + privKey, err := util.DecryptPkcs8PrivateKey(pcc.PrivateKey, flags.keyPassword) + if err != nil { + if err.Error() == "pkcs8: only PBES2 supported" && connector.GetType() == endpoint.ConnectorTypeTPP { + return fmt.Errorf("ERROR: To continue, you must select either the SHA1 3DES or SHA256 AES256 private key PBE algorithm. In a web browser, log in to TLS Protect and go to Configuration > Folders, select your zone, then click Certificate Policy and expand Show Advanced Options to make the change.") + } + return err + } + pcc.PrivateKey = privKey + } + + if pcc.PrivateKey != "" && flags.format == util.LegacyPem && !wasPasswordEmpty { + pcc.PrivateKey, err = util.EncryptPkcs1PrivateKey(pcc.PrivateKey, flags.keyPassword) + if err != nil { + return err + } + } + + if wasPasswordEmpty { + flags.keyPassword = "" + } + + result := &Result{ + Pcc: pcc, + PickupId: flags.pickupID, + Config: &Config{ + Command: c.Command.Name, + Format: flags.format, + JKSAlias: flags.jksAlias, + JKSPassword: flags.jksPassword, + ChainOption: certificate.ChainOptionFromString(flags.chainOption), + AllFile: flags.file, + KeyFile: flags.keyFile, + CertFile: flags.certFile, + ChainFile: flags.chainFile, + PickupIdFile: flags.pickupIDFile, + KeyPassword: flags.keyPassword, + }, + } + err = result.Flush() + + if err != nil { + return fmt.Errorf("Failed to output the results: %s", err) + } + return nil +} + +func doCommandRevoke1(c *cli.Context) error { + err := validateRevokeFlags1(c.Command.Name) + if err != nil { + return err + } + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + var revReq = &certificate.RevocationRequest{} + switch true { + case flags.distinguishedName != "": + revReq.CertificateDN = flags.distinguishedName + revReq.Disable = !flags.noRetire + case flags.thumbprint != "": + revReq.Thumbprint = flags.thumbprint + revReq.Disable = false + default: + return fmt.Errorf("Certificate DN or Thumbprint is required") + } + + requestedFor := func() string { + if flags.distinguishedName != "" { + return flags.distinguishedName + } + if flags.thumbprint != "" { + return flags.thumbprint + } + return "" + }() + + revReq.Reason = flags.revocationReason + revReq.Comments = "revocation request from command line utility" + + err = connector.RevokeCertificate(revReq) + if err != nil { + return fmt.Errorf("Failed to revoke certificate: %s", err) + } + logf("Successfully created revocation request for %s", requestedFor) + + return nil +} + +func doCommandRenew1(c *cli.Context) error { + err := validateRenewFlags1(c.Command.Name) + if err != nil { + return err + } + + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + var req = &certificate.Request{} + var pcc = &certificate.PEMCollection{} + + searchReq := &certificate.Request{ + PickupID: flags.distinguishedName, + Thumbprint: flags.thumbprint, + } + + // here we fetch old cert anyway + oldPcc, err := connector.RetrieveCertificate(searchReq) + if err != nil { + return fmt.Errorf("Failed to fetch old certificate by id %s: %s", flags.distinguishedName, err) + } + oldCertBlock, _ := pem.Decode([]byte(oldPcc.Certificate)) + if oldCertBlock == nil || oldCertBlock.Type != "CERTIFICATE" { + return fmt.Errorf("Failed to fetch old certificate by id %s: PEM parse error", flags.distinguishedName) + } + oldCert, err := x509.ParseCertificate([]byte(oldCertBlock.Bytes)) + if err != nil { + return fmt.Errorf("Failed to fetch old certificate by id %s: %s", flags.distinguishedName, err) + } + // now we have old one + logf("Fetched the latest certificate. Serial: %x, NotAfter: %s", oldCert.SerialNumber, oldCert.NotAfter) + + switch true { + case strings.HasPrefix(flags.csrOption, "file:"): + // will be just sending CSR to backend + req = fillCertificateRequest(req, &flags) + + case "local" == flags.csrOption || "" == flags.csrOption: + // restore certificate request from old certificate + req = certificate.NewRequest(oldCert) + // override values with those from command line flags + req = fillCertificateRequest(req, &flags) + + case "service" == flags.csrOption: + // logger.Panic("service side renewal is not implemented") + req = fillCertificateRequest(req, &flags) + + default: + return fmt.Errorf("unexpected -csr option: %s", flags.csrOption) + } + + // here we ignore zone for Renew action, however, API still needs it + zoneConfig := &endpoint.ZoneConfiguration{} + + err = connector.GenerateRequest(zoneConfig, req) + if err != nil { + return err + } + + requestedFor := func() string { + if flags.distinguishedName != "" { + return flags.distinguishedName + } + if flags.thumbprint != "" { + return flags.thumbprint + } + return "" + }() + + logf("Successfully created request for %s", requestedFor) + + renewReq := generateRenewalRequest(&flags, req) + + flags.pickupID, err = connector.RenewCertificate(renewReq) + + if err != nil { + return err + } + logf("Successfully posted renewal request for %s, will pick up by %s", requestedFor, flags.pickupID) + + passwordAutogenerated := false + if flags.noPickup { + pcc, err = certificate.NewPEMCollection(nil, req.PrivateKey, []byte(flags.keyPassword), flags.format) + if err != nil { + return err + } + } else { + req.PickupID = flags.pickupID + req.ChainOption = certificate.ChainOptionFromString(flags.chainOption) + req.KeyPassword = flags.keyPassword + + if flags.noPrompt && flags.keyPassword == "" && flags.format != P12Format && flags.format != LegacyP12Format && flags.format != JKSFormat && flags.csrOption == "service" { + flags.keyPassword = fmt.Sprintf("t%d-%s.tem.pwd", time.Now().Unix(), randRunes(4)) + req.KeyPassword = flags.keyPassword + passwordAutogenerated = true + } + + req.Timeout = time.Duration(180) * time.Second + pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) + if err != nil { + return err + } + logf("Successfully retrieved request for %s", flags.pickupID) + + if req.CsrOrigin == certificate.LocalGeneratedCSR { + // otherwise private key can be taken from *req + err := pcc.AddPrivateKey(req.PrivateKey, []byte(flags.keyPassword), flags.format) + if err != nil { + log.Fatal(err) + } + } + } + + // Creates a temporary password for service generated csr if following validation is fulfilled. + // Analyzing validation assuming that pkcs12, legacy-pkcs12, jks and service flags are true + //+-------------+----------------+----------------------------------------------------------------------+ + //| --no-prompt | --key-password | What happens in validation? | + //|-------------|----------------|----------------------------------------------------------------------| + //| true | true |VCert will ignore prompt and create a certificate with given password | + //|-------------|----------------|----------------------------------------------------------------------| + //| false | true |VCert will ignore prompt and create a certificate with given password | + //|-------------|----------------|----------------------------------------------------------------------| + //| true | false |VCert will ignore prompt and create a certificate with NO password set| + //|-------------|----------------|----------------------------------------------------------------------| + //| false | false |VCert will prompt to enter password and process will not be completed | + //| | |until password is provided by user | + //+-------------+----------------+----------------------------------------------------------------------+ + if (pcc.PrivateKey != "" && (flags.format == P12Format || flags.format == LegacyP12Format || flags.format == JKSFormat)) || (flags.format == util.LegacyPem && flags.csrOption == "service") || flags.noPrompt && passwordAutogenerated { + privKey, err := util.DecryptPkcs8PrivateKey(pcc.PrivateKey, flags.keyPassword) + if err != nil { + if err.Error() == "pkcs8: only PBES2 supported" && connector.GetType() == endpoint.ConnectorTypeTPP { + return fmt.Errorf("ERROR: To continue, you must select either the SHA1 3DES or SHA256 AES256 private key PBE algorithm. In a web browser, log in to TLS Protect and go to Configuration > Folders, select your zone, then click Certificate Policy and expand Show Advanced Options to make the change.") + } + return err + } + pcc.PrivateKey = privKey + } + + if flags.csrOption == "service" && flags.format == util.LegacyPem && !passwordAutogenerated { + pcc.PrivateKey, err = util.EncryptPkcs1PrivateKey(pcc.PrivateKey, flags.keyPassword) + if err != nil { + return nil + } + } + + // removing temporary password if it was set + if passwordAutogenerated { + flags.keyPassword = "" + } + + // check if previous and renewed certificates are of the same private key + newCertBlock, _ := pem.Decode([]byte(pcc.Certificate)) + if newCertBlock != nil && newCertBlock.Type == "CERTIFICATE" { + newCert, err := x509.ParseCertificate(newCertBlock.Bytes) + if err == nil { + old, _ := json.Marshal(oldCert.PublicKey) + newCrt, _ := json.Marshal(newCert.PublicKey) + if len(old) > 0 && string(old) == string(newCrt) { + logf("WARNING: private key reused") + } + } + } + + result := &Result{ + Pcc: pcc, + PickupId: flags.pickupID, + Config: &Config{ + Command: c.Command.Name, + Format: flags.format, + JKSAlias: flags.jksAlias, + JKSPassword: flags.jksPassword, + ChainOption: certificate.ChainOptionFromString(flags.chainOption), + AllFile: flags.file, + KeyFile: flags.keyFile, + CertFile: flags.certFile, + ChainFile: flags.chainFile, + PickupIdFile: flags.pickupIDFile, + KeyPassword: flags.keyPassword, + }, + } + err = result.Flush() + + if err != nil { + return fmt.Errorf("Failed to output the results: %s", err) + } + return nil +} + +func doCommandRetire(c *cli.Context) error { + err := validateRetireFlags(c.Command.Name) + if err != nil { + return err + } + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + var retReq = &certificate.RetireRequest{} + switch true { + case flags.distinguishedName != "": + retReq.CertificateDN = flags.distinguishedName + case flags.thumbprint != "": + retReq.Thumbprint = flags.thumbprint + default: + return fmt.Errorf("Certificate DN or Thumbprint is required") + } + + requestedFor := func() string { + if flags.distinguishedName != "" { + return flags.distinguishedName + } + if flags.thumbprint != "" { + return flags.thumbprint + } + return "" + }() + + err = connector.RetireCertificate(retReq) + if err != nil { + return fmt.Errorf("Failed to retire certificate: %s", err) + } + logf("Successfully retired certificate for %s", requestedFor) + + return nil +} + +func doCommandGenCSR1(c *cli.Context) error { + err := validateGenerateFlags1(c.Command.Name) + if err != nil { + return err + } + key, csr, err := generateCsrForCommandGenCsr(&flags, []byte(flags.keyPassword)) + if err != nil { + return err + } + err = writeOutKeyAndCsr(c.Command.Name, &flags, key, csr) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/vcert/commands.go b/cmd/vcert/commands.go index 4bb51011..8955bd2d 100644 --- a/cmd/vcert/commands.go +++ b/cmd/vcert/commands.go @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Venafi, Inc. + * Copyright 2020-2024 Venafi, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,184 +19,28 @@ package main import ( "crypto/tls" "crypto/x509" - "encoding/json" "encoding/pem" "fmt" - "log" "net" "net/http" "net/url" "os" - "strconv" "strings" "time" - "github.com/Venafi/vcert/v5/pkg/venafi" "github.com/urfave/cli/v2" "golang.org/x/crypto/pkcs12" - "gopkg.in/yaml.v2" "github.com/Venafi/vcert/v5" "github.com/Venafi/vcert/v5/pkg/certificate" "github.com/Venafi/vcert/v5/pkg/endpoint" - "github.com/Venafi/vcert/v5/pkg/policy" - "github.com/Venafi/vcert/v5/pkg/util" + "github.com/Venafi/vcert/v5/pkg/venafi" "github.com/Venafi/vcert/v5/pkg/venafi/cloud" - "github.com/Venafi/vcert/v5/pkg/venafi/fake" "github.com/Venafi/vcert/v5/pkg/venafi/firefly" "github.com/Venafi/vcert/v5/pkg/venafi/tpp" ) -var ( - tlsConfig tls.Config - - commandEnroll = &cli.Command{ - Before: runBeforeCommand, - Flags: enrollFlags, - Action: doCommandEnroll1, - Name: commandEnrollName, - Usage: "To enroll a certificate", - UsageText: ` vcert enroll - vcert enroll -k -z "\" --cn - vcert enroll -k -z "\" --cn --key-type rsa --key-size 4096 --san-dns --san-dns - vcert enroll --platform vaas -t -z "\" --cn - - vcert enroll -u https://tpp.example.com -t -z "" --cn - vcert enroll -u https://tpp.example.com -t -z "" --cn --key-size 4096 --san-dns --san-dns - vcert enroll -u https://tpp.example.com -t -z "" --cn --key-type ecdsa --key-curve p384 --san-dns -san-dns - vcert enroll -u https://tpp.example.com -z "" --p12-file --p12-password --cn - - vcert enroll --platform firefly -u -t -z "" --cn `, - } - commandGetCred = &cli.Command{ - Before: runBeforeCommand, - Name: commandGetCredName, - Flags: getCredFlags, - Action: doCommandCredMgmt1, - Usage: "To obtain a new credential (token) for authentication", - UsageText: ` vcert getcred --email [--password ] [--format (text|json)] - vcert getcred --platform vaas --tenant-id --external-jwt - - vcert getcred -u https://tpp.example.com --username --password - vcert getcred -u https://tpp.example.com --p12-file --p12-password --trust-bundle /path-to/bundle.pem - vcert getcred -u https://tpp.example.com -t - vcert getcred -u https://tpp.example.com -t --scope - - vcert getcred --platform oidc -u https://authorization-server.com/oauth/token --username --password --scope okta.behaviors.manage - vcert getcred --platform oidc -u https://authorization-server.com/oauth/token --client-id --client-secret --scope okta.behaviors.manage`, - } - commandCheckCred = &cli.Command{ - Before: runBeforeCommand, - Name: commandCheckCredName, - Flags: checkCredFlags, - Action: doCommandCredMgmt1, - Usage: "To verify whether a credential (token) is valid and view its attributes", - UsageText: " vcert checkcred -u https://tpp.example.com -t --trust-bundle /path-to/bundle.pem", - } - commandVoidCred = &cli.Command{ - Before: runBeforeCommand, - Name: commandVoidCredName, - Flags: voidCredFlags, - Action: doCommandCredMgmt1, - Usage: "To invalidate an authentication credential (token)", - UsageText: " vcert voidcred -u https://tpp.example.com -t --trust-bundle /path-to/bundle.pem", - } - commandGenCSR = &cli.Command{ - Before: runBeforeCommand, - Name: commandGenCSRName, - Flags: genCsrFlags, - Action: doCommandGenCSR1, - Usage: "To generate a certificate signing request (CSR)", - UsageText: ` vcert gencsr --cn -o --ou -l --st -c --key-file --csr-file - vcert gencsr --cn -o --ou --ou -l --st -c --key-file --csr-file `, - } - commandPickup = &cli.Command{ - Before: runBeforeCommand, - Name: commandPickupName, - Flags: pickupFlags, - Action: doCommandPickup1, - Usage: "To download a certificate", - UsageText: ` vcert pickup - vcert pickup -k [--pickup-id | --pickup-id-file ] - vcert pickup -u https://tpp.example.com -t --pickup-id `, - } - commandRevoke = &cli.Command{ - Before: runBeforeCommand, - Name: commandRevokeName, - Flags: revokeFlags, - Action: doCommandRevoke1, - Usage: "To revoke a certificate", - UsageText: ` vcert revoke - vcert revoke -u https://tpp.example.com -t --thumbprint - vcert revoke -u https://tpp.example.com -t --id `, - } - commandRenew = &cli.Command{ - Before: runBeforeCommand, - Name: commandRenewName, - Flags: renewFlags, - Action: doCommandRenew1, - Usage: "To renew a certificate", - UsageText: ` vcert renew - vcert renew -u https://tpp.example.com -t --id - vcert renew -k --thumbprint `, - } - commandRetire = &cli.Command{ - Before: runBeforeCommand, - Name: commandRetireName, - Flags: retireFlags, - Action: doCommandRetire, - Usage: "To retire a certificate", - UsageText: ` vcert retire - vcert retire -u https://tpp.example.com -t --id - vcert retire -k --thumbprint `, - } - - commandCreatePolicy = &cli.Command{ - Before: runBeforeCommand, - Name: commandCreatePolicyName, - Flags: createPolicyFlags, - Action: doCommandCreatePolicy, - Usage: "To apply a certificate policy specification to a zone", - UsageText: ` vcert setpolicy - vcert setpolicy -u https://tpp.example.com -t -z "" --file /path-to/policy.spec - vcert setpolicy -k -z "\" --file /path-to/policy.spec`, - } - commandGetPolicy = &cli.Command{ - Before: runBeforeCommand, - Name: commandGetePolicyName, - Flags: getPolicyFlags, - Action: doCommandGetPolicy, - Usage: "To retrieve the certificate policy of a zone", - UsageText: ` vcert getpolicy - vcert getpolicy -u https://tpp.example.com -t -z "" - vcert getpolicy -k -z "\"`, - } - - commandSshPickup = &cli.Command{ - Before: runBeforeCommand, - Name: commandSshPickupName, - Flags: sshPickupFlags, - Action: doCommandSshPickup, - Usage: "To retrieve a SSH Certificate", - UsageText: `vcert sshpickup -u https://tpp.example.com -t --pickup-id `, - } - commandSshEnroll = &cli.Command{ - Before: runBeforeCommand, - Name: commandSshEnrollName, - Flags: sshEnrollFlags, - Action: doCommandEnrollSshCert, - Usage: "To enroll a SSH Certificate", - UsageText: `vcert sshenroll -u https://tpp.example.com -t --template --id --principal bob --principal alice --valid-hours 1`, - } - commandSshGetConfig = &cli.Command{ - Before: runBeforeCommand, - Name: commandSshGetConfigName, - Flags: sshGetConfigFlags, - Action: doCommandSshGetConfig, - Usage: "To get the SSH CA public key and default principals", - UsageText: `vcert sshgetconfig -u https://tpp.example.com -t --template `, - } -) +var tlsConfig tls.Config func runBeforeCommand(c *cli.Context) error { //TODO: move all flag validations here @@ -307,454 +151,6 @@ func setTLSConfig() error { return nil } -func doCommandEnroll1(c *cli.Context) error { - err := validateEnrollFlags(c.Command.Name) - if err != nil { - return err - } - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - var req = &certificate.Request{} - var pcc = &certificate.PEMCollection{} - - zoneConfig, err := connector.ReadZoneConfiguration() - - if err != nil { - return err - } - logf("Successfully read zone configuration for %s", flags.zone) - req = fillCertificateRequest(req, &flags) - err = connector.GenerateRequest(zoneConfig, req) - if err != nil { - return err - } - - var requestedFor string - if req.Subject.CommonName != "" { - requestedFor = req.Subject.CommonName - } else { - requestedFor = flags.csrOption - } - - logf("Successfully created request for %s", requestedFor) - passwordAutogenerated := false - - if connector.SupportSynchronousRequestCertificate() { - pcc, err = connector.SynchronousRequestCertificate(req) - if err != nil { - return err - } - logf("Successfully requested certificate for %s", requestedFor) - } else { - flags.pickupID, err = connector.RequestCertificate(req) - if err != nil { - return err - } - - logf("Successfully posted request for %s, will pick up by %s", requestedFor, flags.pickupID) - - if flags.noPickup { - pcc, err = certificate.NewPEMCollection(nil, req.PrivateKey, []byte(flags.keyPassword), flags.format) - if err != nil { - return err - } - } else { - req.PickupID = flags.pickupID - req.ChainOption = certificate.ChainOptionFromString(flags.chainOption) - req.KeyPassword = flags.keyPassword - - // Creates a temporary password for service generated csr if following validation is fulfilled. - // Analyzing validation assuming that pkcs12, legacy-pkcs12, jks and service flags are true - //+-------------+----------------+----------------------------------------------------------------------+ - //| --no-prompt | --key-password | What happens in validation? | - //|-------------|----------------|----------------------------------------------------------------------| - //| true | true |VCert will ignore prompt and create a certificate with given password | - //|-------------|----------------|----------------------------------------------------------------------| - //| false | true |VCert will ignore prompt and create a certificate with given password | - //|-------------|----------------|----------------------------------------------------------------------| - //| true | false |VCert will ignore prompt and create a certificate with NO password set| - //|-------------|----------------|----------------------------------------------------------------------| - //| false | false |VCert will prompt to enter password and process will not be completed | - //| | |until password is provided by user | - //+-------------+----------------+----------------------------------------------------------------------+ - if flags.noPrompt && flags.keyPassword == "" && flags.format != P12Format && flags.format != LegacyP12Format && flags.format != JKSFormat && flags.csrOption == "service" { - flags.keyPassword = fmt.Sprintf("t%d-%s.tem.pwd", time.Now().Unix(), randRunes(4)) - req.KeyPassword = flags.keyPassword - passwordAutogenerated = true - } - - req.Timeout = time.Duration(180) * time.Second - pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) - if err != nil { - return err - } - logf("Successfully retrieved request for %s", flags.pickupID) - - if req.CsrOrigin == certificate.LocalGeneratedCSR { - // otherwise private key can be taken from *req - err := pcc.AddPrivateKey(req.PrivateKey, []byte(flags.keyPassword), flags.format) - if err != nil { - log.Fatal(err) - } - } - } - } - - if (pcc.PrivateKey != "" && (flags.format == P12Format || flags.format == LegacyP12Format || flags.format == JKSFormat)) || (flags.format == util.LegacyPem && flags.csrOption == "service") || flags.noPrompt && passwordAutogenerated { - privKey, err := util.DecryptPkcs8PrivateKey(pcc.PrivateKey, flags.keyPassword) - if err != nil { - if err.Error() == "pkcs8: only PBES2 supported" && connector.GetType() == endpoint.ConnectorTypeTPP { - return fmt.Errorf("ERROR: To continue, you must select either the SHA1 3DES or SHA256 AES256 private key PBE algorithm. In a web browser, log in to TLS Protect and go to Configuration > Folders, select your zone, then click Certificate Policy and expand Show Advanced Options to make the change.") - } - return err - } - pcc.PrivateKey = privKey - } - - if flags.csrOption == "service" && flags.format == util.LegacyPem && !passwordAutogenerated { - pcc.PrivateKey, err = util.EncryptPkcs1PrivateKey(pcc.PrivateKey, flags.keyPassword) - if err != nil { - return nil - } - } - - // removing temporary password if it was set - if passwordAutogenerated { - flags.keyPassword = "" - } - result := &Result{ - Pcc: pcc, - PickupId: flags.pickupID, - Config: &Config{ - Command: c.Command.Name, - Format: flags.format, - JKSAlias: flags.jksAlias, - JKSPassword: flags.jksPassword, - ChainOption: certificate.ChainOptionFromString(flags.chainOption), - AllFile: flags.file, - KeyFile: flags.keyFile, - CertFile: flags.certFile, - ChainFile: flags.chainFile, - PickupIdFile: flags.pickupIDFile, - KeyPassword: flags.keyPassword, - }, - } - - err = result.Flush() - - if err != nil { - return fmt.Errorf("Failed to output the results: %s", err) - } - return nil -} - -func doCommandEnrollSshCert(c *cli.Context) error { - - err := validateSshEnrollFlags(c.Command.Name) - - if err != nil { - return err - } - - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) - - if err != nil { - logf("Unable to build connector for %s: %s", cfg.ConnectorType, err) - } else { - if flags.verbose { - logf("Successfully built connector for %s", cfg.ConnectorType) - } - } - - err = connector.Ping() - - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - if flags.verbose { - logf("Successfully connected to %s", cfg.ConnectorType) - } - } - - var req = &certificate.SshCertRequest{} - - req = fillSshCertificateRequest(req, &flags) - - if flags.sshCertKeyPassphrase != "" { - flags.keyPassword = flags.sshCertKeyPassphrase - } - - var privateKey, publicKey []byte - sPubKey := "" - //support for local generated keypair or provided public key - if flags.sshCertPubKey == SshCertPubKeyLocal { - - keySize := flags.sshCertKeySize - if keySize <= 0 { - keySize = 3072 - } - - privateKey, publicKey, err = util.GenerateSshKeyPair(keySize, flags.keyPassword, flags.sshCertKeyId, flags.format) - - if err != nil { - return err - } - - sPubKey = string(publicKey) - req.PublicKeyData = sPubKey - } - - if isPubKeyInFile() { - pubKeyS, err := getSshPubKeyFromFile() - - if err != nil { - return err - } - - if pubKeyS == "" { - return fmt.Errorf("specified public key in %s is empty", flags.sshCertPubKey) - } - - req.PublicKeyData = pubKeyS - } - - req.Timeout = time.Duration(flags.timeout) * time.Second - data, err := connector.RequestSSHCertificate(req) - - if err != nil { - return err - } - - // 'Rejected' status is handled in the connector - if (data.ProcessingDetails.Status == "Pending Issue") || (data.ProcessingDetails.Status == "Issued" && data.CertificateData == "") { - logf("SSH certificate was successfully requested. Retrieving the certificate data.") - - flags.pickupID = data.DN - retReq := certificate.SshCertRequest{ - PickupID: flags.pickupID, - IncludeCertificateDetails: true, - } - if flags.keyPassword != "" { - retReq.PrivateKeyPassphrase = flags.keyPassword - } - - retReq.Timeout = time.Duration(10) * time.Second - data, err = connector.RetrieveSSHCertificate(&retReq) - if err != nil { - return fmt.Errorf("Failed to retrieve SSH certificate '%s'. Error: %s", flags.pickupID, err) - } - } else { - logf("Successfully issued SSH certificate with Key ID '%s'", data.CertificateDetails.KeyID) - } - - //this case is when the keypair is local generated - if data.PrivateKeyData == "" { - data.PrivateKeyData = string(privateKey) - } - if sPubKey != "" { - data.PublicKeyData = sPubKey - } - - printSshMetadata(data) - privateKeyS := data.PrivateKeyData - if isServiceGenerated() { - privateKeyS = AddLineEnding(privateKeyS) - } - - privateKeyFileName := flags.sshFileCertEnroll - if privateKeyFileName == "" { - privateKeyFileName = data.CertificateDetails.KeyID - } - - // Check if the files already exist and prompt the user to overwrite - if !flags.noPrompt { - err = validateExistingFile(privateKeyFileName) - if err != nil { - return err - } - } - - err = writeSshFiles(privateKeyFileName, []byte(privateKeyS), []byte(data.PublicKeyData), []byte(data.CertificateData)) - if err != nil { - return err - } - - return nil -} - -func fillSshCertificateRequest(req *certificate.SshCertRequest, cf *commandFlags) *certificate.SshCertRequest { - - if cf.sshCertTemplate != "" { - req.Template = cf.sshCertTemplate - } - - if cf.sshCertKeyId != "" { - req.KeyId = cf.sshCertKeyId - } - - if cf.sshCertObjectName != "" { - req.ObjectName = cf.sshCertObjectName - } - - if cf.sshCertValidHours > 0 { - req.ValidityPeriod = strconv.Itoa(cf.sshCertValidHours) + "h" - } - - if cf.sshCertFolder != "" { - req.PolicyDN = cf.sshCertFolder - } - - if len(cf.sshCertDestAddrs) > 0 { - req.DestinationAddresses = cf.sshCertDestAddrs - } - - if len(cf.sshCertPrincipal) > 0 { - req.Principals = cf.sshCertPrincipal - } - - if len(cf.sshCertExtension) > 0 { - req.Extensions = cf.sshCertExtension - } - - if len(cf.sshCertSourceAddrs) > 0 { - req.SourceAddresses = cf.sshCertSourceAddrs - } - - if cf.sshCertPubKeyData != "" { - req.PublicKeyData = cf.sshCertPubKeyData - } - - if cf.sshCertForceCommand != "" { - req.ForceCommand = cf.sshCertForceCommand - } - - return req -} - -func doCommandCredMgmt1(c *cli.Context) error { - err := validateCredMgmtFlags1(c.Command.Name) - if err != nil { - return err - } - - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - var clientP12 bool - if flags.clientP12 != "" { - clientP12 = true - } - - connector, err := vcert.NewClient(&cfg, false) // Everything else requires an endpoint connection - if err != nil { - return fmt.Errorf("could not create connector: %s", err) - } - - //getting the concrete connector - vaasConnector, okCloud := connector.(*cloud.Connector) - tppConnector, okTPP := connector.(*tpp.Connector) - fireflyConnector, okFirefly := connector.(*firefly.Connector) - _, okFake := connector.(*fake.Connector) //trying to cast to fake.Connector - - if !okCloud && !okTPP && !okFirefly && !okFake { - panic("it was not possible to get a supported connector") - } - - if okFake { - panic("operation is not supported yet") - - } - - switch c.Command.Name { - case commandGetCredName: - if vaasConnector != nil { - return getVaaSCredentials(vaasConnector, &cfg) - } - if tppConnector != nil { - return getTppCredentials(tppConnector, &cfg, clientP12) - } - if fireflyConnector != nil { - return getFireflyCredentials(fireflyConnector, &cfg) - } - case commandCheckCredName: - //TODO: quick workaround to suppress logs when output is in JSON. - if flags.credFormat != "json" { - logf("Checking credentials...") - } - - if cfg.Credentials.AccessToken != "" { - resp, err := tppConnector.VerifyAccessToken(&endpoint.Authentication{ - AccessToken: cfg.Credentials.AccessToken, - }) - if err != nil { - return err - } - if flags.credFormat == "json" { - if err := outputJSON(resp); err != nil { - return err - } - } else { - iso8601fmt := "2006-01-02T15:04:05Z" - tm, _ := time.Parse(iso8601fmt, resp.AccessIssuedOn) - accessExpires := tm.Add(time.Duration(resp.ValidFor) * time.Second).Format(iso8601fmt) - fmt.Println("access_token_expires: ", accessExpires) - fmt.Println("grant_expires: ", resp.Expires) - fmt.Println("client_id: ", resp.ClientID) - fmt.Println("scope: ", resp.Scope) - } - } else { - return fmt.Errorf("Failed to determine credentials set") - } - case commandVoidCredName: - if cfg.Credentials.AccessToken != "" { - err := tppConnector.RevokeAccessToken(&endpoint.Authentication{ - AccessToken: cfg.Credentials.AccessToken, - }) - if err != nil { - return err - } - logf("Access token grant successfully revoked") - } else { - return fmt.Errorf("Failed to determine credentials set") - } - default: - return fmt.Errorf("Unexpected credential operation %s", c.Command.Name) - } - - return nil -} - func getTppCredentials(tppConnector *tpp.Connector, cfg *vcert.Config, clientP12 bool) error { //TODO: quick workaround to suppress logs when output is in JSON. if flags.credFormat != "json" { @@ -920,703 +316,16 @@ func getFireflyCredentials(fireflyConnector *firefly.Connector, cfg *vcert.Confi return nil } -func doCommandGenCSR1(c *cli.Context) error { - err := validateGenerateFlags1(c.Command.Name) - if err != nil { - return err +func generateCsrForCommandGenCsr(cf *commandFlags, privateKeyPass []byte) (privateKey []byte, csr []byte, err error) { + certReq := &certificate.Request{} + if cf.keyType != nil { + certReq.KeyType = *cf.keyType } - key, csr, err := generateCsrForCommandGenCsr(&flags, []byte(flags.keyPassword)) - if err != nil { - return err + certReq.KeyLength = cf.keySize + if cf.keyCurve != certificate.EllipticCurveNotSet { + certReq.KeyCurve = cf.keyCurve } - err = writeOutKeyAndCsr(c.Command.Name, &flags, key, csr) - if err != nil { - return err - } - - return nil -} - -func doCommandPickup1(c *cli.Context) error { - - isServiceGen := IsCSRServiceVaaSGenerated(c.Command.Name) - wasPasswordEmpty := false - if flags.noPrompt && flags.keyPassword == "" && flags.format != P12Format && flags.format != LegacyP12Format && flags.format != JKSFormat && (isServiceGen || isTppConnector(c.Command.Name)) { - flags.keyPassword = fmt.Sprintf("t%d-%s.tem.pwd", time.Now().Unix(), randRunes(4)) - wasPasswordEmpty = true - } - - err := validatePickupFlags1(c.Command.Name) - if err != nil { - return err - } - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - - if flags.pickupIDFile != "" { - bytes, err := os.ReadFile(flags.pickupIDFile) - if err != nil { - return fmt.Errorf("Failed to read Pickup ID value: %s", err) - } - flags.pickupID = strings.TrimSpace(string(bytes)) - } - var req = &certificate.Request{ - PickupID: flags.pickupID, - ChainOption: certificate.ChainOptionFromString(flags.chainOption), - } - if flags.keyPassword != "" { - // key password is provided, which means will be requesting private key - req.KeyPassword = flags.keyPassword - req.FetchPrivateKey = true - } - var pcc *certificate.PEMCollection - pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) - if err != nil { - errStr := err.Error() - sliceString := strings.Split(errStr, ":") - size := len(sliceString) - errToValidate := sliceString[size-1] - if strings.TrimSpace(errToValidate) == "Failed to lookup private key vault id" && wasPasswordEmpty { - req.KeyPassword = "" - req.FetchPrivateKey = false - pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) - - if err != nil { - return fmt.Errorf("Failed to retrieve certificate: %s", err) - } - - } else { - return fmt.Errorf("Failed to retrieve certificate: %s", err) - } - } - logf("Successfully retrieved request for %s", flags.pickupID) - - if pcc.PrivateKey != "" && (flags.format == P12Format || flags.format == LegacyP12Format || flags.format == JKSFormat || flags.format == util.LegacyPem) || (flags.noPrompt && wasPasswordEmpty && pcc.PrivateKey != "") { - privKey, err := util.DecryptPkcs8PrivateKey(pcc.PrivateKey, flags.keyPassword) - if err != nil { - if err.Error() == "pkcs8: only PBES2 supported" && connector.GetType() == endpoint.ConnectorTypeTPP { - return fmt.Errorf("ERROR: To continue, you must select either the SHA1 3DES or SHA256 AES256 private key PBE algorithm. In a web browser, log in to TLS Protect and go to Configuration > Folders, select your zone, then click Certificate Policy and expand Show Advanced Options to make the change.") - } - return err - } - pcc.PrivateKey = privKey - } - - if pcc.PrivateKey != "" && flags.format == util.LegacyPem && !wasPasswordEmpty { - pcc.PrivateKey, err = util.EncryptPkcs1PrivateKey(pcc.PrivateKey, flags.keyPassword) - if err != nil { - return err - } - } - - if wasPasswordEmpty { - flags.keyPassword = "" - } - - result := &Result{ - Pcc: pcc, - PickupId: flags.pickupID, - Config: &Config{ - Command: c.Command.Name, - Format: flags.format, - JKSAlias: flags.jksAlias, - JKSPassword: flags.jksPassword, - ChainOption: certificate.ChainOptionFromString(flags.chainOption), - AllFile: flags.file, - KeyFile: flags.keyFile, - CertFile: flags.certFile, - ChainFile: flags.chainFile, - PickupIdFile: flags.pickupIDFile, - KeyPassword: flags.keyPassword, - }, - } - err = result.Flush() - - if err != nil { - return fmt.Errorf("Failed to output the results: %s", err) - } - return nil -} - -func doCommandRevoke1(c *cli.Context) error { - err := validateRevokeFlags1(c.Command.Name) - if err != nil { - return err - } - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - - var revReq = &certificate.RevocationRequest{} - switch true { - case flags.distinguishedName != "": - revReq.CertificateDN = flags.distinguishedName - revReq.Disable = !flags.noRetire - case flags.thumbprint != "": - revReq.Thumbprint = flags.thumbprint - revReq.Disable = false - default: - return fmt.Errorf("Certificate DN or Thumbprint is required") - } - - requestedFor := func() string { - if flags.distinguishedName != "" { - return flags.distinguishedName - } - if flags.thumbprint != "" { - return flags.thumbprint - } - return "" - }() - - revReq.Reason = flags.revocationReason - revReq.Comments = "revocation request from command line utility" - - err = connector.RevokeCertificate(revReq) - if err != nil { - return fmt.Errorf("Failed to revoke certificate: %s", err) - } - logf("Successfully created revocation request for %s", requestedFor) - - return nil -} - -func doCommandRetire(c *cli.Context) error { - err := validateRetireFlags(c.Command.Name) - if err != nil { - return err - } - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - - var retReq = &certificate.RetireRequest{} - switch true { - case flags.distinguishedName != "": - retReq.CertificateDN = flags.distinguishedName - case flags.thumbprint != "": - retReq.Thumbprint = flags.thumbprint - default: - return fmt.Errorf("Certificate DN or Thumbprint is required") - } - - requestedFor := func() string { - if flags.distinguishedName != "" { - return flags.distinguishedName - } - if flags.thumbprint != "" { - return flags.thumbprint - } - return "" - }() - - err = connector.RetireCertificate(retReq) - if err != nil { - return fmt.Errorf("Failed to retire certificate: %s", err) - } - logf("Successfully retired certificate for %s", requestedFor) - - return nil -} - -func doCommandCreatePolicy(c *cli.Context) error { - - err := validateSetPolicyFlags(c.Command.Name) - - if err != nil { - return err - } - - err = setTLSConfig() - if err != nil { - return err - } - - policyName := flags.policyName - policySpecLocation := flags.policySpecLocation - - logf("Loading policy specification from %s", policySpecLocation) - - file, bytes, err := policy.GetFileAndBytes(policySpecLocation) - - if err != nil { - return err - } - - if flags.verbose { - logf("Policy specification file was successfully opened") - } - - fileExt := policy.GetFileType(policySpecLocation) - fileExt = strings.ToLower(fileExt) - - if flags.verifyPolicyConfig { - err = policy.VerifyPolicySpec(bytes, fileExt) - if err != nil { - err = fmt.Errorf("policy specification file is not valid: %s", err) - return err - } else { - logf("policy specification %s is valid", policySpecLocation) - return nil - } - } - - //based on the extension call the appropriate method to feed the policySpecification - //structure. - var policySpecification policy.PolicySpecification - if fileExt == policy.JsonExtension { - err = json.Unmarshal(bytes, &policySpecification) - if err != nil { - return err - } - } else if fileExt == policy.YamlExtension { - err = yaml.Unmarshal(bytes, &policySpecification) - if err != nil { - return err - } - } else { - return fmt.Errorf("the specified file is not supported") - } - - cfg, err := buildConfig(c, &flags) - - if err != nil { - return fmt.Errorf("failed to build vcert config: %s", err) - } - connector, err := vcert.NewClient(&cfg) - - if err != nil { - return err - } - - _, err = connector.SetPolicy(policyName, &policySpecification) - - defer file.Close() - - return err -} - -func doCommandGetPolicy(c *cli.Context) error { - - err := validateGetPolicyFlags(c.Command.Name) - - if err != nil { - return err - } - - err = setTLSConfig() - if err != nil { - return err - } - - policyName := flags.policyName - - policySpecLocation := flags.policySpecLocation - - var ps *policy.PolicySpecification - - if !flags.policyConfigStarter { - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) - - if err != nil { - - return err - - } - - ps, err = connector.GetPolicy(policyName) - - if err != nil { - return err - } - - } else { - - ps = policy.GetPolicySpec() - - } - - var b []byte - - if policySpecLocation != "" { - - fileExt := policy.GetFileType(policySpecLocation) - fileExt = strings.ToLower(fileExt) - if fileExt == policy.JsonExtension { - b, _ = json.MarshalIndent(ps, "", " ") - if err != nil { - return err - } - } else if fileExt == policy.YamlExtension { - b, _ = yaml.Marshal(ps) - if err != nil { - return err - } - } else { - return fmt.Errorf("the specified byte is not supported") - } - - err = os.WriteFile(policySpecLocation, b, 0600) - if err != nil { - return err - } - log.Printf("policy was written in: %s", policySpecLocation) - - } else { - - b, _ = json.MarshalIndent(ps, "", " ") - - if err != nil { - return err - } - log.Println("Policy is:") - fmt.Println(string(b)) - } - - return nil -} - -func doCommandRenew1(c *cli.Context) error { - err := validateRenewFlags1(c.Command.Name) - if err != nil { - return err - } - - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - - var req = &certificate.Request{} - var pcc = &certificate.PEMCollection{} - - searchReq := &certificate.Request{ - PickupID: flags.distinguishedName, - Thumbprint: flags.thumbprint, - } - - // here we fetch old cert anyway - oldPcc, err := connector.RetrieveCertificate(searchReq) - if err != nil { - return fmt.Errorf("Failed to fetch old certificate by id %s: %s", flags.distinguishedName, err) - } - oldCertBlock, _ := pem.Decode([]byte(oldPcc.Certificate)) - if oldCertBlock == nil || oldCertBlock.Type != "CERTIFICATE" { - return fmt.Errorf("Failed to fetch old certificate by id %s: PEM parse error", flags.distinguishedName) - } - oldCert, err := x509.ParseCertificate([]byte(oldCertBlock.Bytes)) - if err != nil { - return fmt.Errorf("Failed to fetch old certificate by id %s: %s", flags.distinguishedName, err) - } - // now we have old one - logf("Fetched the latest certificate. Serial: %x, NotAfter: %s", oldCert.SerialNumber, oldCert.NotAfter) - - switch true { - case strings.HasPrefix(flags.csrOption, "file:"): - // will be just sending CSR to backend - req = fillCertificateRequest(req, &flags) - - case "local" == flags.csrOption || "" == flags.csrOption: - // restore certificate request from old certificate - req = certificate.NewRequest(oldCert) - // override values with those from command line flags - req = fillCertificateRequest(req, &flags) - - case "service" == flags.csrOption: - // logger.Panic("service side renewal is not implemented") - req = fillCertificateRequest(req, &flags) - - default: - return fmt.Errorf("unexpected -csr option: %s", flags.csrOption) - } - - // here we ignore zone for Renew action, however, API still needs it - zoneConfig := &endpoint.ZoneConfiguration{} - - err = connector.GenerateRequest(zoneConfig, req) - if err != nil { - return err - } - - requestedFor := func() string { - if flags.distinguishedName != "" { - return flags.distinguishedName - } - if flags.thumbprint != "" { - return flags.thumbprint - } - return "" - }() - - logf("Successfully created request for %s", requestedFor) - - renewReq := generateRenewalRequest(&flags, req) - - flags.pickupID, err = connector.RenewCertificate(renewReq) - - if err != nil { - return err - } - logf("Successfully posted renewal request for %s, will pick up by %s", requestedFor, flags.pickupID) - - passwordAutogenerated := false - if flags.noPickup { - pcc, err = certificate.NewPEMCollection(nil, req.PrivateKey, []byte(flags.keyPassword), flags.format) - if err != nil { - return err - } - } else { - req.PickupID = flags.pickupID - req.ChainOption = certificate.ChainOptionFromString(flags.chainOption) - req.KeyPassword = flags.keyPassword - - if flags.noPrompt && flags.keyPassword == "" && flags.format != P12Format && flags.format != LegacyP12Format && flags.format != JKSFormat && flags.csrOption == "service" { - flags.keyPassword = fmt.Sprintf("t%d-%s.tem.pwd", time.Now().Unix(), randRunes(4)) - req.KeyPassword = flags.keyPassword - passwordAutogenerated = true - } - - req.Timeout = time.Duration(180) * time.Second - pcc, err = retrieveCertificate(connector, req, time.Duration(flags.timeout)*time.Second) - if err != nil { - return err - } - logf("Successfully retrieved request for %s", flags.pickupID) - - if req.CsrOrigin == certificate.LocalGeneratedCSR { - // otherwise private key can be taken from *req - err := pcc.AddPrivateKey(req.PrivateKey, []byte(flags.keyPassword), flags.format) - if err != nil { - log.Fatal(err) - } - } - } - - // Creates a temporary password for service generated csr if following validation is fulfilled. - // Analyzing validation assuming that pkcs12, legacy-pkcs12, jks and service flags are true - //+-------------+----------------+----------------------------------------------------------------------+ - //| --no-prompt | --key-password | What happens in validation? | - //|-------------|----------------|----------------------------------------------------------------------| - //| true | true |VCert will ignore prompt and create a certificate with given password | - //|-------------|----------------|----------------------------------------------------------------------| - //| false | true |VCert will ignore prompt and create a certificate with given password | - //|-------------|----------------|----------------------------------------------------------------------| - //| true | false |VCert will ignore prompt and create a certificate with NO password set| - //|-------------|----------------|----------------------------------------------------------------------| - //| false | false |VCert will prompt to enter password and process will not be completed | - //| | |until password is provided by user | - //+-------------+----------------+----------------------------------------------------------------------+ - if (pcc.PrivateKey != "" && (flags.format == P12Format || flags.format == LegacyP12Format || flags.format == JKSFormat)) || (flags.format == util.LegacyPem && flags.csrOption == "service") || flags.noPrompt && passwordAutogenerated { - privKey, err := util.DecryptPkcs8PrivateKey(pcc.PrivateKey, flags.keyPassword) - if err != nil { - if err.Error() == "pkcs8: only PBES2 supported" && connector.GetType() == endpoint.ConnectorTypeTPP { - return fmt.Errorf("ERROR: To continue, you must select either the SHA1 3DES or SHA256 AES256 private key PBE algorithm. In a web browser, log in to TLS Protect and go to Configuration > Folders, select your zone, then click Certificate Policy and expand Show Advanced Options to make the change.") - } - return err - } - pcc.PrivateKey = privKey - } - - if flags.csrOption == "service" && flags.format == util.LegacyPem && !passwordAutogenerated { - pcc.PrivateKey, err = util.EncryptPkcs1PrivateKey(pcc.PrivateKey, flags.keyPassword) - if err != nil { - return nil - } - } - - // removing temporary password if it was set - if passwordAutogenerated { - flags.keyPassword = "" - } - - // check if previous and renewed certificates are of the same private key - newCertBlock, _ := pem.Decode([]byte(pcc.Certificate)) - if newCertBlock != nil && newCertBlock.Type == "CERTIFICATE" { - newCert, err := x509.ParseCertificate(newCertBlock.Bytes) - if err == nil { - old, _ := json.Marshal(oldCert.PublicKey) - newCrt, _ := json.Marshal(newCert.PublicKey) - if len(old) > 0 && string(old) == string(newCrt) { - logf("WARNING: private key reused") - } - } - } - - result := &Result{ - Pcc: pcc, - PickupId: flags.pickupID, - Config: &Config{ - Command: c.Command.Name, - Format: flags.format, - JKSAlias: flags.jksAlias, - JKSPassword: flags.jksPassword, - ChainOption: certificate.ChainOptionFromString(flags.chainOption), - AllFile: flags.file, - KeyFile: flags.keyFile, - CertFile: flags.certFile, - ChainFile: flags.chainFile, - PickupIdFile: flags.pickupIDFile, - KeyPassword: flags.keyPassword, - }, - } - err = result.Flush() - - if err != nil { - return fmt.Errorf("Failed to output the results: %s", err) - } - return nil -} - -func doCommandSshGetConfig(c *cli.Context) error { - - err := validateGetSshConfigFlags(c.Command.Name) - - if err != nil { - return err - } - - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) - - if err != nil { - strErr := (err).Error() - if strErr != "vcert error: your data contains problems: auth error: failed to authenticate: can't determine valid credentials set" { - logf("Unable to build connector for %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully built connector for %s", cfg.ConnectorType) - } - } else { - logf("Successfully built connector for %s", cfg.ConnectorType) - } - - err = connector.Ping() - - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - - req := &certificate.SshCaTemplateRequest{} - if flags.sshCertTemplate != "" { - req.Template = flags.sshCertTemplate - } - if flags.sshCertGuid != "" { - req.Guid = flags.sshCertGuid - } - - conf, err := connector.RetrieveSshConfig(req) - if err != nil { - return err - } - - fmt.Println() - fmt.Println("CA public key:") - fmt.Println(conf.CaPublicKey) - - if len(conf.Principals) > 0 { - fmt.Println() - fmt.Println("Principals:") - for _, v := range conf.Principals { - fmt.Println(v) - } - } - - if flags.sshFileGetConfig != "" { - // Check if the file already exists and prompt the user to overwrite - if !flags.noPrompt { - err = validateExistingFile(flags.sshFileGetConfig) - if err != nil { - return err - } - } - - err = writeToFile([]byte(conf.CaPublicKey), flags.sshFileGetConfig, 0600) - if err != nil { - return err - } - } - - return nil -} - -func generateCsrForCommandGenCsr(cf *commandFlags, privateKeyPass []byte) (privateKey []byte, csr []byte, err error) { - certReq := &certificate.Request{} - if cf.keyType != nil { - certReq.KeyType = *cf.keyType - } - certReq.KeyLength = cf.keySize - if cf.keyCurve != certificate.EllipticCurveNotSet { - certReq.KeyCurve = cf.keyCurve - } - err = certReq.GeneratePrivateKey() + err = certReq.GeneratePrivateKey() if err != nil { return } @@ -1673,91 +382,3 @@ func writeOutKeyAndCsr(commandName string, cf *commandFlags, key []byte, csr []b err = result.Flush() return } - -func doCommandSshPickup(c *cli.Context) error { - - err := validateSshRetrieveFlags(c.Command.Name) - - if err != nil { - return err - } - - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - - var req certificate.SshCertRequest - - req = buildSshCertRequest(req, &flags) - - req.Timeout = time.Duration(10) * time.Second - data, err := connector.RetrieveSSHCertificate(&req) - - if err != nil { - return fmt.Errorf("failed to retrieve certificate: %s", err) - } - logf("Successfully retrieved request for %s", data.DN) - - printSshMetadata(data) - privateKeyS := data.PrivateKeyData - if privateKeyS != "" { - privateKeyS = AddLineEnding(privateKeyS) - } - - // If --file is not set, use Key ID as filename - privateKeyFileName := flags.sshFileCertEnroll - if privateKeyFileName == "" { - privateKeyFileName = data.CertificateDetails.KeyID - } - - // Check if the files already exist and prompt the user to overwrite - if !flags.noPrompt { - err = validateExistingFile(privateKeyFileName) - if err != nil { - return err - } - } - - err = writeSshFiles(privateKeyFileName, []byte(privateKeyS), []byte(data.PublicKeyData), []byte(data.CertificateData)) - if err != nil { - return err - } - - return nil -} - -func buildSshCertRequest(r certificate.SshCertRequest, cf *commandFlags) certificate.SshCertRequest { - - if cf.sshCertKeyPassphrase != "" { - cf.keyPassword = cf.sshCertKeyPassphrase - } - - if cf.sshCertPickupId != "" { - r.PickupID = cf.sshCertPickupId - } - - if cf.sshCertGuid != "" { - r.Guid = cf.sshCertGuid - } - - if cf.keyPassword != "" { - r.PrivateKeyPassphrase = cf.keyPassword - } - - r.IncludeCertificateDetails = true - - return r -} diff --git a/cmd/vcert/credentials.go b/cmd/vcert/credentials.go new file mode 100644 index 00000000..b54c5d28 --- /dev/null +++ b/cmd/vcert/credentials.go @@ -0,0 +1,170 @@ +/* + * Copyright 2020-2024 Venafi, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "time" + + "github.com/urfave/cli/v2" + + "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/venafi/cloud" + "github.com/Venafi/vcert/v5/pkg/venafi/fake" + "github.com/Venafi/vcert/v5/pkg/venafi/firefly" + "github.com/Venafi/vcert/v5/pkg/venafi/tpp" +) + +var ( + commandGetCred = &cli.Command{ + Before: runBeforeCommand, + Name: commandGetCredName, + Flags: getCredFlags, + Action: doCommandCredMgmt1, + Usage: "To obtain a new access token for authentication", + UsageText: ` vcert getcred --email [--password ] [--format (text|json)] + vcert getcred -p vcp --token-url --idp-jwt + + vcert getcred -u https://tpp.example.com --username --password + vcert getcred -u https://tpp.example.com --p12-file --p12-password --trust-bundle /path-to/bundle.pem + vcert getcred -u https://tpp.example.com -t + vcert getcred -u https://tpp.example.com -t --scope + vcert getcred -p tpp -u https://tpp.example.com -t + + vcert getcred -p oidc -u https://authorization-server.com/oauth/token --username --password --scope okta.behaviors.manage + vcert getcred -p oidc -u https://authorization-server.com/oauth/token --client-id --client-secret --scope okta.behaviors.manage`, + } + + commandCheckCred = &cli.Command{ + Before: runBeforeCommand, + Name: commandCheckCredName, + Flags: checkCredFlags, + Action: doCommandCredMgmt1, + Usage: "To verify whether a Trust Protection Platform access token is valid and view its attributes", + UsageText: " vcert checkcred -u https://tpp.example.com -t --trust-bundle /path-to/bundle.pem", + } + + commandVoidCred = &cli.Command{ + Before: runBeforeCommand, + Name: commandVoidCredName, + Flags: voidCredFlags, + Action: doCommandCredMgmt1, + Usage: "To invalidate a Trust Protection Platform access token", + UsageText: " vcert voidcred -u https://tpp.example.com -t --trust-bundle /path-to/bundle.pem", + } +) + +func doCommandCredMgmt1(c *cli.Context) error { + err := validateCredMgmtFlags1(c.Command.Name) + if err != nil { + return err + } + + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + var clientP12 bool + if flags.clientP12 != "" { + clientP12 = true + } + + connector, err := vcert.NewClient(&cfg, false) // Everything else requires an endpoint connection + if err != nil { + return fmt.Errorf("could not create connector: %s", err) + } + + //getting the concrete connector + vaasConnector, okCloud := connector.(*cloud.Connector) + tppConnector, okTPP := connector.(*tpp.Connector) + fireflyConnector, okFirefly := connector.(*firefly.Connector) + _, okFake := connector.(*fake.Connector) //trying to cast to fake.Connector + + if !okCloud && !okTPP && !okFirefly && !okFake { + panic("it was not possible to get a supported connector") + } + + if okFake { + panic("operation is not supported yet") + + } + + switch c.Command.Name { + case commandGetCredName: + if vaasConnector != nil { + return getVaaSCredentials(vaasConnector, &cfg) + } + if tppConnector != nil { + return getTppCredentials(tppConnector, &cfg, clientP12) + } + if fireflyConnector != nil { + return getFireflyCredentials(fireflyConnector, &cfg) + } + case commandCheckCredName: + //TODO: quick workaround to suppress logs when output is in JSON. + if flags.credFormat != "json" { + logf("Checking credentials...") + } + + if cfg.Credentials.AccessToken != "" { + resp, err := tppConnector.VerifyAccessToken(&endpoint.Authentication{ + AccessToken: cfg.Credentials.AccessToken, + }) + if err != nil { + return err + } + if flags.credFormat == "json" { + if err := outputJSON(resp); err != nil { + return err + } + } else { + iso8601fmt := "2006-01-02T15:04:05Z" + tm, _ := time.Parse(iso8601fmt, resp.AccessIssuedOn) + accessExpires := tm.Add(time.Duration(resp.ValidFor) * time.Second).Format(iso8601fmt) + fmt.Println("access_token_expires: ", accessExpires) + fmt.Println("grant_expires: ", resp.Expires) + fmt.Println("client_id: ", resp.ClientID) + fmt.Println("scope: ", resp.Scope) + } + } else { + return fmt.Errorf("Failed to determine credentials set") + } + case commandVoidCredName: + if cfg.Credentials.AccessToken != "" { + err := tppConnector.RevokeAccessToken(&endpoint.Authentication{ + AccessToken: cfg.Credentials.AccessToken, + }) + if err != nil { + return err + } + logf("Access token grant successfully revoked") + } else { + return fmt.Errorf("Failed to determine credentials set") + } + default: + return fmt.Errorf("Unexpected credential operation %s", c.Command.Name) + } + + return nil +} diff --git a/cmd/vcert/main.go b/cmd/vcert/main.go index 6f8e9c2e..fecc6fe0 100644 --- a/cmd/vcert/main.go +++ b/cmd/vcert/main.go @@ -106,26 +106,26 @@ AUTHOR: {{range .Authors}}{{ . }} {{end}}{{end}}{{if .Commands}} ACTIONS: - Command Platform Description - - gencsr To generate a certificate signing request (CSR) - enroll TPP | VaaS | FF To enroll a certificate - pickup TPP | VaaS To retrieve a certificate - renew TPP | VaaS To renew a certificate - retire TPP | VaaS To retire a certificate - revoke TPP To revoke a certificate - run TPP | VaaS | FF To retrieve and install certificates using a vcert playbook file - - getpolicy TPP | VaaS To retrieve the certificate policy of a zone - setpolicy TPP | VaaS To apply a certificate policy specification to a zone - - getcred TPP | VaaS | FF To obtain a new authentication token from any Venafi platform or to register for a new VaaS user API key - checkcred TPP To check the validity of a token and grant. Only works for TPP platform - voidcred TPP To invalidate an authentication grant. Only works for TPP platform - - sshenroll TPP To enroll a SSH certificate - sshpickup TPP To retrieve a SSH certificate - sshgetconfig TPP To get the SSH CA public key and default principals + Command Platform Description + + gencsr To generate a certificate signing request (CSR) + enroll tpp | vcp | firefly To enroll a certificate + pickup tpp | vcp To retrieve a certificate + renew tpp | vcp To renew a certificate + retire tpp | vcp To retire a certificate + revoke tpp To revoke a certificate + run tpp | vcp | firefly To retrieve and install certificates using a vcert playbook file + + getpolicy tpp | vcp To retrieve the certificate policy of a zone + setpolicy tpp | vcp To apply a certificate policy specification to a zone + + getcred tpp | vcp | oidc To obtain a new authentication token from any Venafi platform or to register for a new Venafi Control Plane user API key + checkcred tpp To check the validity of a Trust Protection Platform token and grant + voidcred tpp To invalidate a Trust Protection Platform authentication token + + sshenroll tpp To enroll an SSH certificate + sshpickup tpp To retrieve an SSH certificate + sshgetconfig tpp To get the SSH CA public key and default principals OPTIONS: {{range .VisibleFlags}}{{.}} diff --git a/cmd/vcert/policies.go b/cmd/vcert/policies.go new file mode 100644 index 00000000..f9875307 --- /dev/null +++ b/cmd/vcert/policies.go @@ -0,0 +1,218 @@ +/* + * Copyright 2020-2024 Venafi, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "strings" + + "github.com/urfave/cli/v2" + "gopkg.in/yaml.v2" + + "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/policy" +) + +var ( + commandCreatePolicy = &cli.Command{ + Before: runBeforeCommand, + Name: commandCreatePolicyName, + Flags: createPolicyFlags, + Action: doCommandCreatePolicy, + Usage: "To apply a certificate policy specification to a zone", + UsageText: ` vcert setpolicy + vcert setpolicy -u https://tpp.example.com -t -z "" --file /path-to/policy.spec + vcert setpolicy -p vcp -t -z "\" --file /path-to/policy.spec`, + } + + commandGetPolicy = &cli.Command{ + Before: runBeforeCommand, + Name: commandGetePolicyName, + Flags: getPolicyFlags, + Action: doCommandGetPolicy, + Usage: "To retrieve the certificate policy of a zone", + UsageText: ` vcert getpolicy + vcert getpolicy -u https://tpp.example.com -t -z "" + vcert getpolicy -p vcp -t -z "\"`, + } +) + +func doCommandCreatePolicy(c *cli.Context) error { + + err := validateSetPolicyFlags(c.Command.Name) + + if err != nil { + return err + } + + err = setTLSConfig() + if err != nil { + return err + } + + policyName := flags.policyName + policySpecLocation := flags.policySpecLocation + + logf("Loading policy specification from %s", policySpecLocation) + + file, bytes, err := policy.GetFileAndBytes(policySpecLocation) + + if err != nil { + return err + } + + if flags.verbose { + logf("Policy specification file was successfully opened") + } + + fileExt := policy.GetFileType(policySpecLocation) + fileExt = strings.ToLower(fileExt) + + if flags.verifyPolicyConfig { + err = policy.VerifyPolicySpec(bytes, fileExt) + if err != nil { + err = fmt.Errorf("policy specification file is not valid: %s", err) + return err + } else { + logf("policy specification %s is valid", policySpecLocation) + return nil + } + } + + //based on the extension call the appropriate method to feed the policySpecification + //structure. + var policySpecification policy.PolicySpecification + if fileExt == policy.JsonExtension { + err = json.Unmarshal(bytes, &policySpecification) + if err != nil { + return err + } + } else if fileExt == policy.YamlExtension { + err = yaml.Unmarshal(bytes, &policySpecification) + if err != nil { + return err + } + } else { + return fmt.Errorf("the specified file is not supported") + } + + cfg, err := buildConfig(c, &flags) + + if err != nil { + return fmt.Errorf("failed to build vcert config: %s", err) + } + connector, err := vcert.NewClient(&cfg) + + if err != nil { + return err + } + + _, err = connector.SetPolicy(policyName, &policySpecification) + + defer file.Close() + + return err +} + +func doCommandGetPolicy(c *cli.Context) error { + + err := validateGetPolicyFlags(c.Command.Name) + + if err != nil { + return err + } + + err = setTLSConfig() + if err != nil { + return err + } + + policyName := flags.policyName + + policySpecLocation := flags.policySpecLocation + + var ps *policy.PolicySpecification + + if !flags.policyConfigStarter { + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) + + if err != nil { + + return err + + } + + ps, err = connector.GetPolicy(policyName) + + if err != nil { + return err + } + + } else { + + ps = policy.GetPolicySpec() + + } + + var b []byte + + if policySpecLocation != "" { + + fileExt := policy.GetFileType(policySpecLocation) + fileExt = strings.ToLower(fileExt) + if fileExt == policy.JsonExtension { + b, _ = json.MarshalIndent(ps, "", " ") + if err != nil { + return err + } + } else if fileExt == policy.YamlExtension { + b, _ = yaml.Marshal(ps) + if err != nil { + return err + } + } else { + return fmt.Errorf("the specified byte is not supported") + } + + err = os.WriteFile(policySpecLocation, b, 0600) + if err != nil { + return err + } + log.Printf("policy was written in: %s", policySpecLocation) + + } else { + + b, _ = json.MarshalIndent(ps, "", " ") + + if err != nil { + return err + } + log.Println("Policy is:") + fmt.Println(string(b)) + } + + return nil +} diff --git a/cmd/vcert/sshcertificates.go b/cmd/vcert/sshcertificates.go new file mode 100644 index 00000000..93acad7a --- /dev/null +++ b/cmd/vcert/sshcertificates.go @@ -0,0 +1,421 @@ +/* + * Copyright 2020-2024 Venafi, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "strconv" + "time" + + "github.com/Venafi/vcert/v5/pkg/util" + "github.com/urfave/cli/v2" + + "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/certificate" +) + +var ( + commandSshPickup = &cli.Command{ + Before: runBeforeCommand, + Name: commandSshPickupName, + Flags: sshPickupFlags, + Action: doCommandSSHPickup, + Usage: "To retrieve an SSH Certificate from Trust Protection Platform", + UsageText: `vcert sshpickup -u https://tpp.example.com -t --pickup-id `, + } + + commandSshEnroll = &cli.Command{ + Before: runBeforeCommand, + Name: commandSshEnrollName, + Flags: sshEnrollFlags, + Action: doCommandSSHEnroll, + Usage: "To enroll an SSH Certificate to Trust Protection Platform", + UsageText: `vcert sshenroll -u https://tpp.example.com -t --template --id --principal bob --principal alice --valid-hours 1`, + } + + commandSshGetConfig = &cli.Command{ + Before: runBeforeCommand, + Name: commandSshGetConfigName, + Flags: sshGetConfigFlags, + Action: doCommandSSHGetConfig, + Usage: "To get the SSH CA public key and default principals from Trust Protection Platform", + UsageText: `vcert sshgetconfig -u https://tpp.example.com -t --template `, + } +) + +func doCommandSSHPickup(c *cli.Context) error { + + err := validateSshRetrieveFlags(c.Command.Name) + + if err != nil { + return err + } + + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) // Everything else requires an endpoint connection + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + var req certificate.SshCertRequest + + req = buildSSHCertificateRequest(req, &flags) + + req.Timeout = time.Duration(10) * time.Second + data, err := connector.RetrieveSSHCertificate(&req) + + if err != nil { + return fmt.Errorf("failed to retrieve certificate: %s", err) + } + logf("Successfully retrieved request for %s", data.DN) + + printSshMetadata(data) + privateKeyS := data.PrivateKeyData + if privateKeyS != "" { + privateKeyS = AddLineEnding(privateKeyS) + } + + // If --file is not set, use Key ID as filename + privateKeyFileName := flags.sshFileCertEnroll + if privateKeyFileName == "" { + privateKeyFileName = data.CertificateDetails.KeyID + } + + // Check if the files already exist and prompt the user to overwrite + if !flags.noPrompt { + err = validateExistingFile(privateKeyFileName) + if err != nil { + return err + } + } + + err = writeSshFiles(privateKeyFileName, []byte(privateKeyS), []byte(data.PublicKeyData), []byte(data.CertificateData)) + if err != nil { + return err + } + + return nil +} + +func doCommandSSHEnroll(c *cli.Context) error { + + err := validateSshEnrollFlags(c.Command.Name) + + if err != nil { + return err + } + + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) + + if err != nil { + logf("Unable to build connector for %s: %s", cfg.ConnectorType, err) + } else { + if flags.verbose { + logf("Successfully built connector for %s", cfg.ConnectorType) + } + } + + err = connector.Ping() + + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + if flags.verbose { + logf("Successfully connected to %s", cfg.ConnectorType) + } + } + + var req = &certificate.SshCertRequest{} + + req = fillSSHCertificateRequest(req, &flags) + + if flags.sshCertKeyPassphrase != "" { + flags.keyPassword = flags.sshCertKeyPassphrase + } + + var privateKey, publicKey []byte + sPubKey := "" + //support for local generated keypair or provided public key + if flags.sshCertPubKey == SshCertPubKeyLocal { + + keySize := flags.sshCertKeySize + if keySize <= 0 { + keySize = 3072 + } + + privateKey, publicKey, err = util.GenerateSshKeyPair(keySize, flags.keyPassword, flags.sshCertKeyId, flags.format) + + if err != nil { + return err + } + + sPubKey = string(publicKey) + req.PublicKeyData = sPubKey + } + + if isPubKeyInFile() { + pubKeyS, err := getSshPubKeyFromFile() + + if err != nil { + return err + } + + if pubKeyS == "" { + return fmt.Errorf("specified public key in %s is empty", flags.sshCertPubKey) + } + + req.PublicKeyData = pubKeyS + } + + req.Timeout = time.Duration(flags.timeout) * time.Second + data, err := connector.RequestSSHCertificate(req) + + if err != nil { + return err + } + + // 'Rejected' status is handled in the connector + if (data.ProcessingDetails.Status == "Pending Issue") || (data.ProcessingDetails.Status == "Issued" && data.CertificateData == "") { + logf("SSH certificate was successfully requested. Retrieving the certificate data.") + + flags.pickupID = data.DN + retReq := certificate.SshCertRequest{ + PickupID: flags.pickupID, + IncludeCertificateDetails: true, + } + if flags.keyPassword != "" { + retReq.PrivateKeyPassphrase = flags.keyPassword + } + + retReq.Timeout = time.Duration(10) * time.Second + data, err = connector.RetrieveSSHCertificate(&retReq) + if err != nil { + return fmt.Errorf("Failed to retrieve SSH certificate '%s'. Error: %s", flags.pickupID, err) + } + } else { + logf("Successfully issued SSH certificate with Key ID '%s'", data.CertificateDetails.KeyID) + } + + //this case is when the keypair is local generated + if data.PrivateKeyData == "" { + data.PrivateKeyData = string(privateKey) + } + if sPubKey != "" { + data.PublicKeyData = sPubKey + } + + printSshMetadata(data) + privateKeyS := data.PrivateKeyData + if isServiceGenerated() { + privateKeyS = AddLineEnding(privateKeyS) + } + + privateKeyFileName := flags.sshFileCertEnroll + if privateKeyFileName == "" { + privateKeyFileName = data.CertificateDetails.KeyID + } + + // Check if the files already exist and prompt the user to overwrite + if !flags.noPrompt { + err = validateExistingFile(privateKeyFileName) + if err != nil { + return err + } + } + + err = writeSshFiles(privateKeyFileName, []byte(privateKeyS), []byte(data.PublicKeyData), []byte(data.CertificateData)) + if err != nil { + return err + } + + return nil +} + +func doCommandSSHGetConfig(c *cli.Context) error { + + err := validateGetSshConfigFlags(c.Command.Name) + + if err != nil { + return err + } + + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) + + if err != nil { + strErr := (err).Error() + if strErr != "vcert error: your data contains problems: auth error: failed to authenticate: can't determine valid credentials set" { + logf("Unable to build connector for %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully built connector for %s", cfg.ConnectorType) + } + } else { + logf("Successfully built connector for %s", cfg.ConnectorType) + } + + err = connector.Ping() + + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + req := &certificate.SshCaTemplateRequest{} + if flags.sshCertTemplate != "" { + req.Template = flags.sshCertTemplate + } + if flags.sshCertGuid != "" { + req.Guid = flags.sshCertGuid + } + + conf, err := connector.RetrieveSshConfig(req) + if err != nil { + return err + } + + fmt.Println() + fmt.Println("CA public key:") + fmt.Println(conf.CaPublicKey) + + if len(conf.Principals) > 0 { + fmt.Println() + fmt.Println("Principals:") + for _, v := range conf.Principals { + fmt.Println(v) + } + } + + if flags.sshFileGetConfig != "" { + // Check if the file already exists and prompt the user to overwrite + if !flags.noPrompt { + err = validateExistingFile(flags.sshFileGetConfig) + if err != nil { + return err + } + } + + err = writeToFile([]byte(conf.CaPublicKey), flags.sshFileGetConfig, 0600) + if err != nil { + return err + } + } + + return nil +} + +func buildSSHCertificateRequest(r certificate.SshCertRequest, cf *commandFlags) certificate.SshCertRequest { + + if cf.sshCertKeyPassphrase != "" { + cf.keyPassword = cf.sshCertKeyPassphrase + } + + if cf.sshCertPickupId != "" { + r.PickupID = cf.sshCertPickupId + } + + if cf.sshCertGuid != "" { + r.Guid = cf.sshCertGuid + } + + if cf.keyPassword != "" { + r.PrivateKeyPassphrase = cf.keyPassword + } + + r.IncludeCertificateDetails = true + + return r +} + +func fillSSHCertificateRequest(req *certificate.SshCertRequest, cf *commandFlags) *certificate.SshCertRequest { + + if cf.sshCertTemplate != "" { + req.Template = cf.sshCertTemplate + } + + if cf.sshCertKeyId != "" { + req.KeyId = cf.sshCertKeyId + } + + if cf.sshCertObjectName != "" { + req.ObjectName = cf.sshCertObjectName + } + + if cf.sshCertValidHours > 0 { + req.ValidityPeriod = strconv.Itoa(cf.sshCertValidHours) + "h" + } + + if cf.sshCertFolder != "" { + req.PolicyDN = cf.sshCertFolder + } + + if len(cf.sshCertDestAddrs) > 0 { + req.DestinationAddresses = cf.sshCertDestAddrs + } + + if len(cf.sshCertPrincipal) > 0 { + req.Principals = cf.sshCertPrincipal + } + + if len(cf.sshCertExtension) > 0 { + req.Extensions = cf.sshCertExtension + } + + if len(cf.sshCertSourceAddrs) > 0 { + req.SourceAddresses = cf.sshCertSourceAddrs + } + + if cf.sshCertPubKeyData != "" { + req.PublicKeyData = cf.sshCertPubKeyData + } + + if cf.sshCertForceCommand != "" { + req.ForceCommand = cf.sshCertForceCommand + } + + return req +} diff --git a/pkg/venafi/platform.go b/pkg/venafi/platform.go index ecfa11bf..a4e97a6f 100644 --- a/pkg/venafi/platform.go +++ b/pkg/venafi/platform.go @@ -31,7 +31,7 @@ const ( Undefined Platform = iota // Fake is a fake platform for tests Fake - // TLSPCloud represents the TLS Protect Cloud platform type + // TLSPCloud represents the VCP platform type TLSPCloud // TPP represents the TPP platform type TPP @@ -51,12 +51,11 @@ const ( strPlatformTLSPC = "TLSPC" // alias for VCP strPlatformVaaS = "VAAS" - //NOTE: For now OIDC will be taken as an alias for Firefly - //given Firefly implements the logic to get an OAuth 2.0 - //Access token but OIDC will be available independently of Firefly - //so is pending to create an independent client to get an - //OAuth 2.0 access token - // alias for Firefly + // NOTE: For now OIDC will be taken as an alias for Firefly + // given Firefly implements the logic to get an OAuth 2.0 + // access token but OIDC will be available independently of Firefly. + // So is pending to create an independent client to get an + // OAuth 2.0 access token strPlatformOIDC = "OIDC" ) From 6b182efceeeb6a715739b9dca10269da9c83552d Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Thu, 4 Apr 2024 17:09:37 -0600 Subject: [PATCH 4/6] feat(svc-account): Updated docs with service-account authentication details --- cmd/vcert/envVars.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/vcert/envVars.go b/cmd/vcert/envVars.go index e0347105..07996b70 100644 --- a/cmd/vcert/envVars.go +++ b/cmd/vcert/envVars.go @@ -59,7 +59,7 @@ var ( { EnvVarName: vCertIdPJWT, Destination: &flags.idPJWT, - FlagName: "--external-jwt", + FlagName: "--idp-jwt", }, { EnvVarName: vCertTrustBundle, From 4b44d2d4d3410efaa9884b9c75482dd3c9b42a71 Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Thu, 4 Apr 2024 17:21:10 -0600 Subject: [PATCH 5/6] feat(svc-account): Fixes linter issue for tokenURL variable --- cmd/vcert/envVars.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/vcert/envVars.go b/cmd/vcert/envVars.go index 07996b70..12eb1f49 100644 --- a/cmd/vcert/envVars.go +++ b/cmd/vcert/envVars.go @@ -9,7 +9,7 @@ const ( vCertToken = "VCERT_TOKEN" // #nosec G101 vCertApiKey = "VCERT_APIKEY" // #nosec G101 vCertIdPJWT = "VCERT_IDP_JWT" - vCertTokenURL = "VCERT_TOKEN_URL" + vCertTokenURL = "VCERT_TOKEN_URL" // #nosec G101 vCertTrustBundle = "VCERT_TRUST_BUNDLE" vcertUser = "VCERT_USER" vcertPassword = "VCERT_PASSWORD" From 254ff6d761e675085a3712082c8d9ade16f7b4a2 Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Thu, 4 Apr 2024 18:19:11 -0600 Subject: [PATCH 6/6] feat(svc-account): Fixes broken unit test --- client_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client_test.go b/client_test.go index 49d9377b..006de7a1 100644 --- a/client_test.go +++ b/client_test.go @@ -316,8 +316,10 @@ func TestNewClient_UserAgent(t *testing.T) { name: "with-service-account", args: []any{ &endpoint.Authentication{ - TenantID: "fake-tenant-id", - ExternalIdPJWT: "fake-external-id-pjwt", + IdPJWT: "fake-external-idp-jwt", + IdentityProvider: &endpoint.OAuthProvider{ + TokenURL: "https://fake.token.url.com/token", + }, }, }, },