1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

Use Conjur API's built in JWT functions (#3771)

* Use Conjur API's built in JWT functions

Signed-off-by: Shlomo Heigh <shlomo.heigh@cyberark.com>

* docs: clarify that all Conjur types are supported

Signed-off-by: Shlomo Heigh <shlomo.heigh@cyberark.com>

* docs: add link to Conjur blog post

Signed-off-by: Shlomo Heigh <shlomo.heigh@cyberark.com>

---------

Signed-off-by: Shlomo Heigh <shlomo.heigh@cyberark.com>
This commit is contained in:
Shlomo Zalman Heigh 2024-08-28 15:54:04 -04:00 committed by GitHub
parent 1707de3d5a
commit a1722cbfaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 129 deletions

View file

@ -2,6 +2,11 @@
A list of blogs written by people all over the community. Feel free to let us know if you are writing about ESO at some place! We would be happy to mention you here!
## [Enhancing Kubernetes Security and Flexibility with the CyberArk Conjur and ESO Integration](https://developer.cyberark.com/blog/enhancing-kubernetes-security-and-flexibility-with-the-cyberark-conjur-and-eso-integration/)
[@szh](https://github.com/szh) Writes about using ESO with CyberArk Conjur. He includes detailed steps on how to
set up a local environment with Docker Desktop and how to deploy ESO and Conjur OSS on it.
## [Comparing External Secrets Operator with Secret Storage CSI as Kubernetes External Secrets is Deprecated](https://mixi-developers.mixi.co.jp/compare-eso-with-secret-csi-402bf37f20bc)
@riddle writes about choosing ESO when comparing with Secret Store CSI Driver in their specific use case. They show us the relevant differences between the projects when looking at their scenario and requirements while integrating with ArgoCD. [Comparing External Secrets Operator with Secret Storage CSI as Kubernetes External Secrets is Deprecated](https://mixi-developers.mixi.co.jp/compare-eso-with-secret-csi-402bf37f20bc)

View file

@ -6,7 +6,9 @@ This section describes how to set up the Conjur provider for External Secrets Op
Before installing the Conjur provider, you need:
* A running Conjur Server, with:
* A running Conjur Server ([OSS](https://github.com/cyberark/conjur),
[Enterprise](https://www.cyberark.com/products/secrets-manager-enterprise/), or
[Cloud](https://www.cyberark.com/products/multi-cloud-secrets/)), with:
* An accessible Conjur endpoint (for example: `https://myapi.example.com`).
* Your configured Conjur authentication info (such as `hostid`, `apikey`, or JWT service ID). For more information on configuring Conjur, see [Policy statement reference](https://docs.cyberark.com/conjur-open-source/Latest/en/Content/Operations/Policy/policy-statement-ref.htm).
* Support for your authentication method (`apikey` is supported by default, `jwt` requires additional configuration).

View file

@ -16,14 +16,9 @@ package conjur
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/http"
"time"
"github.com/cyberark/conjur-api-go/conjurapi"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -88,31 +83,3 @@ func (c *Client) getJwtFromServiceAccountTokenRequest(ctx context.Context, servi
}
return tokenResponse.Status.Token, nil
}
// newClientFromJwt creates a new Conjur client using the given JWT Auth Config.
func (c *Client) newClientFromJwt(ctx context.Context, config conjurapi.Config, jwtAuth *esv1beta1.ConjurJWT) (SecretsClient, error) {
jwtToken, getJWTError := c.getJWTToken(ctx, jwtAuth)
if getJWTError != nil {
return nil, getJWTError
}
client, clientError := c.clientAPI.NewClientFromJWT(config, jwtToken, jwtAuth.ServiceID, jwtAuth.HostID)
if clientError != nil {
return nil, clientError
}
return client, nil
}
// newHTTPSClient creates a new HTTPS client with the given cert.
func newHTTPSClient(cert []byte) (*http.Client, error) {
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM(cert)
if !ok {
return nil, errors.New("can't append Conjur SSL cert")
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12},
}
return &http.Client{Transport: tr, Timeout: time.Second * 10}, nil
}

View file

@ -78,48 +78,9 @@ func (c *Client) GetConjurClient(ctx context.Context) (SecretsClient, error) {
}
if prov.Auth.APIKey != nil {
config.Account = prov.Auth.APIKey.Account
conjUser, secErr := resolvers.SecretKeyRef(
ctx,
c.kube,
c.StoreKind,
c.namespace, prov.Auth.APIKey.UserRef)
if secErr != nil {
return nil, fmt.Errorf(errBadServiceUser, secErr)
}
conjAPIKey, secErr := resolvers.SecretKeyRef(
ctx,
c.kube,
c.StoreKind,
c.namespace,
prov.Auth.APIKey.APIKeyRef)
if secErr != nil {
return nil, fmt.Errorf(errBadServiceAPIKey, secErr)
}
conjur, newClientFromKeyError := c.clientAPI.NewClientFromKey(config,
authn.LoginPair{
Login: conjUser,
APIKey: conjAPIKey,
},
)
if newClientFromKeyError != nil {
return nil, fmt.Errorf(errConjurClient, newClientFromKeyError)
}
c.client = conjur
return conjur, nil
return c.conjurClientFromAPIKey(ctx, config, prov)
} else if prov.Auth.Jwt != nil {
config.Account = prov.Auth.Jwt.Account
conjur, clientFromJwtError := c.newClientFromJwt(ctx, config, prov.Auth.Jwt)
if clientFromJwtError != nil {
return nil, fmt.Errorf(errConjurClient, clientFromJwtError)
}
c.client = conjur
return conjur, nil
return c.conjurClientFromJWT(ctx, config, prov)
} else {
// Should not happen because validate func should catch this
return nil, errors.New("no authentication method provided")
@ -150,3 +111,59 @@ func (c *Client) Validate() (esv1beta1.ValidationResult, error) {
func (c *Client) Close(_ context.Context) error {
return nil
}
func (c *Client) conjurClientFromAPIKey(ctx context.Context, config conjurapi.Config, prov *esv1beta1.ConjurProvider) (SecretsClient, error) {
config.Account = prov.Auth.APIKey.Account
conjUser, secErr := resolvers.SecretKeyRef(
ctx,
c.kube,
c.StoreKind,
c.namespace, prov.Auth.APIKey.UserRef)
if secErr != nil {
return nil, fmt.Errorf(errBadServiceUser, secErr)
}
conjAPIKey, secErr := resolvers.SecretKeyRef(
ctx,
c.kube,
c.StoreKind,
c.namespace,
prov.Auth.APIKey.APIKeyRef)
if secErr != nil {
return nil, fmt.Errorf(errBadServiceAPIKey, secErr)
}
conjur, newClientFromKeyError := c.clientAPI.NewClientFromKey(config,
authn.LoginPair{
Login: conjUser,
APIKey: conjAPIKey,
},
)
if newClientFromKeyError != nil {
return nil, fmt.Errorf(errConjurClient, newClientFromKeyError)
}
c.client = conjur
return conjur, nil
}
func (c *Client) conjurClientFromJWT(ctx context.Context, config conjurapi.Config, prov *esv1beta1.ConjurProvider) (SecretsClient, error) {
config.AuthnType = "jwt"
config.Account = prov.Auth.Jwt.Account
config.JWTHostID = prov.Auth.Jwt.HostID
config.ServiceID = prov.Auth.Jwt.ServiceID
jwtToken, getJWTError := c.getJWTToken(ctx, prov.Auth.Jwt)
if getJWTError != nil {
return nil, getJWTError
}
config.JWTContent = jwtToken
conjur, clientError := c.clientAPI.NewClientFromJWT(config)
if clientError != nil {
return nil, fmt.Errorf(errConjurClient, clientError)
}
c.client = conjur
return conjur, nil
}

View file

@ -15,15 +15,8 @@ limitations under the License.
package conjur
import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/cyberark/conjur-api-go/conjurapi"
"github.com/cyberark/conjur-api-go/conjurapi/authn"
"github.com/cyberark/conjur-api-go/conjurapi/response"
)
// SecretsClient is an interface for the Conjur client.
@ -36,7 +29,7 @@ type SecretsClient interface {
// SecretsClientFactory is an interface for creating a Conjur client.
type SecretsClientFactory interface {
NewClientFromKey(config conjurapi.Config, loginPair authn.LoginPair) (SecretsClient, error)
NewClientFromJWT(config conjurapi.Config, jwtToken string, jwtServiceID, jwtHostID string) (SecretsClient, error)
NewClientFromJWT(config conjurapi.Config) (SecretsClient, error)
}
// ClientAPIImpl is an implementation of the ClientAPI interface.
@ -47,49 +40,6 @@ func (c *ClientAPIImpl) NewClientFromKey(config conjurapi.Config, loginPair auth
}
// NewClientFromJWT creates a new Conjur client from a JWT token.
// cannot use the built-in function "conjurapi.NewClientFromJwt" because it requires environment variables
// see: https://github.com/cyberark/conjur-api-go/blob/b698692392a38e5d38b8440f32ab74206544848a/conjurapi/client.go#L130
func (c *ClientAPIImpl) NewClientFromJWT(config conjurapi.Config, jwtToken, jwtServiceID, jwtHostID string) (SecretsClient, error) {
jwtTokenString := fmt.Sprintf("jwt=%s", jwtToken)
var httpClient *http.Client
if config.IsHttps() {
cert, err := config.ReadSSLCert()
if err != nil {
return nil, err
}
httpClient, err = newHTTPSClient(cert)
if err != nil {
return nil, err
}
} else {
httpClient = &http.Client{Timeout: time.Second * 10}
}
var authnJwtURL string
// If a hostID is provided, it must be included in the URL
if jwtHostID != "" {
authnJwtURL = strings.Join([]string{config.ApplianceURL, "authn-jwt", jwtServiceID, config.Account, url.PathEscape(jwtHostID), "authenticate"}, "/")
} else {
authnJwtURL = strings.Join([]string{config.ApplianceURL, "authn-jwt", jwtServiceID, config.Account, "authenticate"}, "/")
}
req, err := http.NewRequest("POST", authnJwtURL, strings.NewReader(jwtTokenString))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
tokenBytes, err := response.DataResponse(resp)
if err != nil {
return nil, err
}
return conjurapi.NewClientFromToken(config, string(tokenBytes))
func (c *ClientAPIImpl) NewClientFromJWT(config conjurapi.Config) (SecretsClient, error) {
return conjurapi.NewClientFromJwt(config)
}

View file

@ -709,7 +709,7 @@ func (c *ConjurMockAPIClient) NewClientFromKey(_ conjurapi.Config, _ authn.Login
return &fake.ConjurMockClient{}, nil
}
func (c *ConjurMockAPIClient) NewClientFromJWT(_ conjurapi.Config, _, _, _ string) (SecretsClient, error) {
func (c *ConjurMockAPIClient) NewClientFromJWT(_ conjurapi.Config) (SecretsClient, error) {
return &fake.ConjurMockClient{}, nil
}