google_guest_agent/agentcrypto/mtls_mds_linux.go (88 lines of code) (raw):
// Copyright 2023 Google LLC
// 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
// https://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 agentcrypto
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run"
"github.com/GoogleCloudPlatform/guest-agent/utils"
"github.com/GoogleCloudPlatform/guest-logging-go/logger"
)
const (
// defaultCredsDir is the directory location for MTLS MDS credentials.
defaultCredsDir = "/run/google-mds-mtls"
// rootCACertFileName is the root CA cert.
rootCACertFileName = "root.crt"
// clientCredsFileName are client credentials, its basically the file
// that has the EC private key and the client certificate concatenated.
clientCredsFileName = "client.key"
)
var (
// certUpdaters is a map of known CA certificate updaters with the local directory paths for certificates.
certUpdaters = map[string][]string{
// SUSE, Debian and Ubuntu distributions.
// https://manpages.ubuntu.com/manpages/xenial/man8/update-ca-certificates.8.html
// https://github.com/openSUSE/ca-certificates
"update-ca-certificates": {"/usr/local/share/ca-certificates", "/usr/share/pki/trust/anchors"},
// CentOS, Fedora, RedHat distributions.
// https://www.unix.com/man-page/centos/8/UPDATE-CA-TRUST
"update-ca-trust": {"/etc/pki/ca-trust/source/anchors"},
}
)
// writeRootCACert writes Root CA cert from UEFI variable to output file.
func (j *CredsJob) writeRootCACert(ctx context.Context, content []byte, outputFile string) error {
// The directory should be executable, but the file does not need to be.
if err := os.MkdirAll(filepath.Dir(outputFile), 0655); err != nil {
return err
}
if err := utils.SaferWriteFile(content, outputFile, 0644); err != nil {
return err
}
if !j.useNativeStore.Load() {
logger.Debugf("SkipNativeStore is enabled, will not write root cert to system store")
return nil
}
// Best effort to update system store, don't fail.
if err := updateSystemStore(ctx, outputFile); err != nil {
logger.Errorf("Failed add Root MDS cert to system trust store with error: %v", err)
}
return nil
}
// writeClientCredentials stores client credentials (certificate and private key).
func (j *CredsJob) writeClientCredentials(plaintext []byte, outputFile string) error {
// The directory should be executable, but the file does not need to be.
if err := os.MkdirAll(filepath.Dir(outputFile), 0655); err != nil {
return err
}
return utils.SaferWriteFile(plaintext, outputFile, 0644)
}
// getCAStoreUpdater interates over known system trust store updaters and returns the first found.
func getCAStoreUpdater() (string, error) {
var errs []string
for u := range certUpdaters {
_, err := exec.LookPath(u)
if err == nil {
return u, nil
}
errs = append(errs, fmt.Sprintf("lookup for %q failed with error: %v", u, err))
}
return "", fmt.Errorf("no known trust updaters were found: %v", errs)
}
// certificateDirFromUpdater returns directory of local CA certificates for the given updater tool.
func certificateDirFromUpdater(updater string) (string, error) {
dirs, ok := certUpdaters[updater]
if !ok {
return "", fmt.Errorf("unknown updater %q, no local trusted CA certificate directory found", updater)
}
for _, dir := range dirs {
fi, err := os.Stat(dir)
if err == nil && fi.IsDir() {
return dir, nil
}
}
return "", fmt.Errorf("no of the known directories %v found for updater %q", dirs, updater)
}
// updateSystemStore updates the local system store with the cert.
func updateSystemStore(ctx context.Context, cert string) error {
cmd, err := getCAStoreUpdater()
if err != nil {
return err
}
dir, err := certificateDirFromUpdater(cmd)
if err != nil {
return err
}
dest := filepath.Join(dir, filepath.Base(cert))
if err := utils.CopyFile(cert, dest, 0644); err != nil {
return err
}
res := run.WithOutput(ctx, cmd)
if res.ExitCode != 0 {
return fmt.Errorf("command %q failed with error: %s", cmd, res.Error())
}
logger.Infof("Certificate %q added to system store successfully %s", cert, res.StdOut)
return nil
}