-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Istio CA integrates with Vault CA providers (#10638)
* Istio integrates with Vault CA providers * Add tests to the Vault CA client * Add the support to TLS Vault connection * Add a test for a TLS Vault server * Change to use a Vault TLS server in the endpoints-jenkins project * Change the vault client name * Fix goimports error and logf statements * Configure the test Vault server * Change naming in client.go * Add flags of Vault CA config
- Loading branch information
1 parent
fe94f7f
commit f97a89f
Showing
9 changed files
with
677 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
226 changes: 226 additions & 0 deletions
226
security/pkg/nodeagent/caclient/providers/vault/client.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
// Copyright 2018 Istio Authors | ||
// | ||
// 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 caclient | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"fmt" | ||
"net/http" | ||
"strconv" | ||
|
||
"github.com/hashicorp/vault/api" | ||
|
||
"istio.io/istio/pkg/log" | ||
caClientInterface "istio.io/istio/security/pkg/nodeagent/caclient/interface" | ||
) | ||
|
||
type vaultClient struct { | ||
enableTLS bool | ||
tlsRootCert []byte | ||
|
||
vaultAddr string | ||
vaultLoginRole string | ||
vaultLoginPath string | ||
vaultSignCsrPath string | ||
|
||
client *api.Client | ||
} | ||
|
||
// NewVaultClient create a CA client for the Vault provider 1. | ||
func NewVaultClient(tls bool, tlsRootCert []byte, | ||
vaultAddr, vaultLoginRole, vaultLoginPath, vaultSignCsrPath string) (caClientInterface.Client, error) { | ||
c := &vaultClient{ | ||
enableTLS: tls, | ||
tlsRootCert: tlsRootCert, | ||
vaultAddr: vaultAddr, | ||
vaultLoginRole: vaultLoginRole, | ||
vaultLoginPath: vaultLoginPath, | ||
vaultSignCsrPath: vaultSignCsrPath, | ||
} | ||
|
||
var client *api.Client | ||
var err error | ||
if tls { | ||
client, err = createVaultTLSClient(vaultAddr, tlsRootCert) | ||
} else { | ||
client, err = createVaultClient(vaultAddr) | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
c.client = client | ||
|
||
return c, nil | ||
} | ||
|
||
// CSR Sign calls Vault to sign a CSR. | ||
func (c *vaultClient) CSRSign(ctx context.Context, csrPEM []byte, saToken string, | ||
certValidTTLInSec int64) ([]string /*PEM-encoded certificate chain*/, error) { | ||
token, err := loginVaultK8sAuthMethod(c.client, c.vaultLoginPath, c.vaultLoginRole, saToken) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to login Vault: %v", err) | ||
} | ||
c.client.SetToken(token) | ||
certChain, err := signCsrByVault(c.client, c.vaultSignCsrPath, certValidTTLInSec, csrPEM) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to sign CSR: %v", err) | ||
} | ||
|
||
if len(certChain) <= 1 { | ||
log.Errorf("certificate chain length is %d, expected more than 1", len(certChain)) | ||
return nil, fmt.Errorf("invalid certificate chain in the response") | ||
} | ||
|
||
return certChain, nil | ||
} | ||
|
||
// createVaultClient creates a client to a Vault server | ||
// vaultAddr: the address of the Vault server (e.g., "http://127.0.0.1:8200"). | ||
func createVaultClient(vaultAddr string) (*api.Client, error) { | ||
config := api.DefaultConfig() | ||
config.Address = vaultAddr | ||
|
||
client, err := api.NewClient(config) | ||
if err != nil { | ||
log.Errorf("failed to create a Vault client: %v", err) | ||
return nil, err | ||
} | ||
|
||
return client, nil | ||
} | ||
|
||
// createVaultTLSClient creates a client to a Vault server | ||
// vaultAddr: the address of the Vault server (e.g., "https://127.0.0.1:8200"). | ||
func createVaultTLSClient(vaultAddr string, tlsRootCert []byte) (*api.Client, error) { | ||
// Load the system default root certificates. | ||
pool, err := x509.SystemCertPool() | ||
if err != nil { | ||
log.Errorf("could not get SystemCertPool: %v", err) | ||
return nil, fmt.Errorf("could not get SystemCertPool: %v", err) | ||
} | ||
if tlsRootCert != nil && len(tlsRootCert) > 0 { | ||
ok := pool.AppendCertsFromPEM(tlsRootCert) | ||
if !ok { | ||
return nil, fmt.Errorf("failed to append a certificate (%v) to the certificate pool", string(tlsRootCert[:])) | ||
} | ||
} | ||
tlsConfig := &tls.Config{ | ||
RootCAs: pool, | ||
} | ||
|
||
transport := &http.Transport{TLSClientConfig: tlsConfig} | ||
httpClient := &http.Client{Transport: transport} | ||
|
||
config := api.DefaultConfig() | ||
config.Address = vaultAddr | ||
config.HttpClient = httpClient | ||
|
||
client, err := api.NewClient(config) | ||
if err != nil { | ||
log.Errorf("failed to create a Vault client: %v", err) | ||
return nil, err | ||
} | ||
|
||
return client, nil | ||
} | ||
|
||
// loginVaultK8sAuthMethod logs into the Vault k8s auth method with the service account and | ||
// returns the auth client token. | ||
// loginPath: the path of the login | ||
// role: the login role | ||
// jwt: the service account used for login | ||
func loginVaultK8sAuthMethod(client *api.Client, loginPath, role, sa string) (string, error) { | ||
resp, err := client.Logical().Write( | ||
loginPath, | ||
map[string]interface{}{ | ||
"jwt": sa, | ||
"role": role, | ||
}) | ||
|
||
if err != nil { | ||
log.Errorf("failed to login Vault: %v", err) | ||
return "", err | ||
} | ||
if resp == nil { | ||
log.Errorf("login response is nil") | ||
return "", fmt.Errorf("login response is nil") | ||
} | ||
if resp.Auth == nil { | ||
log.Errorf("login response auth field is nil") | ||
return "", fmt.Errorf("login response auth field is nil") | ||
} | ||
return resp.Auth.ClientToken, nil | ||
} | ||
|
||
// signCsrByVault signs the CSR and return the signed certifcate and the CA certificate chain | ||
// Return the signed certificate chain when succeed. | ||
// client: the Vault client | ||
// csrSigningPath: the path for signing a CSR | ||
// csr: the CSR to be signed, in pem format | ||
func signCsrByVault(client *api.Client, csrSigningPath string, certTTLInSec int64, csr []byte) ([]string, error) { | ||
m := map[string]interface{}{ | ||
"format": "pem", | ||
"csr": string(csr[:]), | ||
"ttl": strconv.FormatInt(certTTLInSec, 10) + "s", | ||
} | ||
res, err := client.Logical().Write(csrSigningPath, m) | ||
if err != nil { | ||
log.Errorf("failed to post to %v: %v", csrSigningPath, err) | ||
return nil, fmt.Errorf("failed to post to %v: %v", csrSigningPath, err) | ||
} | ||
if res == nil { | ||
log.Error("sign response is nil") | ||
return nil, fmt.Errorf("sign response is nil") | ||
} | ||
if res.Data == nil { | ||
log.Error("sign response has a nil Data field") | ||
return nil, fmt.Errorf("sign response has a nil Data field") | ||
} | ||
//Extract the certificate and the certificate chain | ||
certificate, ok := res.Data["certificate"] | ||
if !ok { | ||
log.Error("no certificate in the CSR response") | ||
return nil, fmt.Errorf("no certificate in the CSR response") | ||
} | ||
cert, ok := certificate.(string) | ||
if !ok { | ||
log.Error("the certificate in the CSR response is not a string") | ||
return nil, fmt.Errorf("the certificate in the CSR response is not a string") | ||
} | ||
caChain, ok := res.Data["ca_chain"] | ||
if !ok { | ||
log.Error("no certificate chain in the CSR response") | ||
return nil, fmt.Errorf("no certificate chain in the CSR response") | ||
} | ||
chain, ok := caChain.([]interface{}) | ||
if !ok { | ||
log.Error("the certificate chain in the CSR response is of unexpected format") | ||
return nil, fmt.Errorf("the certificate chain in the CSR response is of unexpected format") | ||
} | ||
var certChain []string | ||
certChain = append(certChain, cert) | ||
for idx, c := range chain { | ||
_, ok := c.(string) | ||
if !ok { | ||
log.Errorf("the certificate in the certificate chain %v is not a string", idx) | ||
return nil, fmt.Errorf("the certificate in the certificate chain %v is not a string", idx) | ||
} | ||
certChain = append(certChain, c.(string)) | ||
} | ||
|
||
return certChain, nil | ||
} |
Oops, something went wrong.