feat: Add opt-in support for proxy CA cert installation (#318)

* feat: Add opt-in support for proxy CA cert installation

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* docs: Add limitation for MacOS MDM script
This commit is contained in:
Abhisek Datta
2026-06-03 23:15:02 +05:30
committed by GitHub
parent c3f3920d2e
commit 4f0db15ede
31 changed files with 1806 additions and 77 deletions
+326
View File
@@ -0,0 +1,326 @@
package setup
import (
"errors"
"fmt"
"io"
"os"
"strconv"
"time"
"github.com/safedep/dry/log"
"github.com/safedep/dry/usefulerror"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/errcodes"
"github.com/safedep/pmg/internal/ui"
"github.com/safedep/pmg/proxy/certmanager"
"github.com/safedep/pmg/truststore"
"github.com/spf13/cobra"
)
// trustStore is the testability seam; defaultTrustStore delegates to the package.
type trustStore interface {
Install(certPEM []byte, scope truststore.Scope) error
Uninstall(commonName string, scope truststore.Scope) error
Status(commonName string) (user, system bool, err error)
UserScopeSupported() bool
}
type defaultTrustStore struct{}
func (defaultTrustStore) Install(p []byte, s truststore.Scope) error { return truststore.Install(p, s) }
func (defaultTrustStore) Uninstall(cn string, s truststore.Scope) error {
return truststore.Uninstall(cn, s)
}
func (defaultTrustStore) Status(cn string) (bool, bool, error) { return truststore.Status(cn) }
func (defaultTrustStore) UserScopeSupported() bool { return truststore.UserScopeSupported() }
type certCommandError struct{ usefulerror.UsefulError }
func (e *certCommandError) ExitCode() int { return 1 }
func newCertCommandError(code, msg, help string, cause error) *certCommandError {
return &certCommandError{
UsefulError: usefulerror.NewUsefulError().
WithCode(code).
WithHumanError(msg).
WithHelp(help).
Wrap(cause),
}
}
// NewCertCommand returns the `pmg setup cert` command tree.
func NewCertCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "cert",
Short: "Manage PMG's MITM CA certificate and OS trust",
Long: "Generate, persist, and trust PMG's MITM CA so package managers and " +
"native tools (including Go on macOS/Windows) trust HTTPS interception.",
RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() },
}
cmd.AddCommand(newCertInstallCommand())
cmd.AddCommand(newCertUninstallCommand())
cmd.AddCommand(newCertStatusCommand())
return cmd
}
func scopeFromFlag(system bool) truststore.Scope {
if system {
return truststore.ScopeSystem
}
return truststore.ScopeUser
}
func newCertInstallCommand() *cobra.Command {
var system, force bool
cmd := &cobra.Command{
Use: "install",
Short: "Generate, persist, and trust PMG's MITM CA",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := errIfRunningUnderSudo(); err != nil {
return err
}
return runCertInstall(config.Get().ConfigDir(), scopeFromFlag(system), force, defaultTrustStore{}, os.Stdout)
},
}
cmd.Flags().BoolVar(&system, "system", false, "Install into the system (all-users) trust store (PMG prompts for elevation; on Windows run from an elevated prompt)")
cmd.Flags().BoolVar(&force, "force", false, "Regenerate and re-trust the CA even if one already exists")
return cmd
}
func newCertUninstallCommand() *cobra.Command {
var system, purge bool
cmd := &cobra.Command{
Use: "uninstall",
Short: "Remove PMG's MITM CA from the OS trust store",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := errIfRunningUnderSudo(); err != nil {
return err
}
return runCertUninstall(config.Get().ConfigDir(), scopeFromFlag(system), purge, defaultTrustStore{}, os.Stdout)
},
}
cmd.Flags().BoolVar(&system, "system", false, "Remove from the system (all-users) trust store (PMG prompts for elevation; on Windows run from an elevated prompt)")
cmd.Flags().BoolVar(&purge, "purge", false, "Also delete the on-disk CA keypair")
return cmd
}
func newCertStatusCommand() *cobra.Command {
return &cobra.Command{
Use: "status",
Short: "Show PMG MITM CA presence, trust scope, and expiry",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if err := errIfRunningUnderSudo(); err != nil {
return err
}
return runCertStatus(config.Get().ConfigDir(), defaultTrustStore{}, os.Stdout)
},
}
}
func runCertInstall(dir string, scope truststore.Scope, force bool, store trustStore, out io.Writer) error {
caCert, loadErr := certmanager.LoadCA(dir)
exists := loadErr == nil
// A failed load with files still on disk means a corrupt or partial CA (e.g.
// the cert is present/trusted but ca-key.pem is missing or unparseable).
// Treat it like a rotation so the old trusted root is cleaned up rather than
// left behind alongside a freshly generated one. (A missing key surfaces as
// os.ErrNotExist from LoadCA, so checking on-disk remnants is what tells a
// partial state apart from a truly fresh install.)
diskState, inspectErr := certmanager.InspectCA(dir)
if inspectErr != nil {
log.Debugf("inspecting on-disk CA during install: %v", inspectErr)
}
corrupted := loadErr != nil && (diskState.KeyPresent || diskState.CertPresent)
expired := exists && caCert.IsExpired(time.Hour)
rotate := force || expired || corrupted
if corrupted {
log.Debugf("replacing unreadable persisted CA: %v", loadErr)
}
if exists && !rotate {
user, system, _ := store.Status(certmanager.CACommonName)
if (scope == truststore.ScopeUser && user) || (scope == truststore.ScopeSystem && system) {
if _, err := fmt.Fprintf(out, "%s CA already installed and trusted (%s scope)\n", ui.Colors.Green("✓"), scope.String()); err != nil {
return err
}
return nil
}
}
if !exists || rotate {
if rotate && (exists || corrupted) {
msg := "Rotating existing CA"
if corrupted {
msg = "Persisted CA is incomplete or unreadable; replacing it"
}
if _, err := fmt.Fprintf(out, "%s %s\n", ui.Colors.Dim(""), msg); err != nil {
return err
}
if err := store.Uninstall(certmanager.CACommonName, scope); err != nil && !errors.Is(err, truststore.ErrUserScopeUnsupported) {
return newCertCommandError(errcodes.CertTrustStore, "failed to remove old CA before rotation", trustHelp(scope), err)
}
// Best-effort cleanup of the opposite scope so a scope change during
// rotation leaves no stale same-CN cert behind. Non-fatal: the other
// scope may need privileges we do not hold.
if err := store.Uninstall(certmanager.CACommonName, otherScope(scope)); err != nil && !errors.Is(err, truststore.ErrUserScopeUnsupported) {
log.Debugf("best-effort cleanup of %s-scope CA during rotation failed: %v", otherScope(scope), err)
}
}
generated, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
if err != nil {
return newCertCommandError(errcodes.CertGeneration, "failed to generate CA certificate",
"Check available entropy and try again", err)
}
if err := certmanager.SaveCA(dir, generated); err != nil {
return newCertCommandError(errcodes.CertPersistence, "failed to persist CA keypair",
fmt.Sprintf("Check write permissions for %s", dir), err)
}
caCert = generated
if _, err := fmt.Fprintf(out, "%s CA keypair written to %s\n", ui.Colors.Green("✓"), certmanager.CACertPath(dir)); err != nil {
return err
}
} else {
if _, err := fmt.Fprintf(out, "%s Reusing existing CA keypair at %s\n", ui.Colors.Dim(""), certmanager.CACertPath(dir)); err != nil {
return err
}
}
if _, err := fmt.Fprintf(out, "%s Installing an OS-trusted MITM CA (%s scope). This lets PMG inspect HTTPS package traffic.\n",
ui.Colors.Yellow("⚠"), scope.String()); err != nil {
return err
}
if err := store.Install(caCert.Certificate, scope); err != nil {
if errors.Is(err, truststore.ErrUserScopeUnsupported) {
// Linux has no per-user trust store; treat as a friendly no-op.
if _, err := fmt.Fprintf(out, "%s %s\n", ui.Colors.Dim(""),
"This platform has no per-user trust store. The CA keypair is persisted. "+
"Re-run with --system for machine-wide trust (PMG prompts for elevation)."); err != nil {
return err
}
return nil
}
return newCertCommandError(errcodes.CertTrustStore, "failed to install CA into trust store", trustHelp(scope), err)
}
if _, err := fmt.Fprintf(out, "%s CA installed and trusted (%s scope)\n", ui.Colors.Green("✓"), scope.String()); err != nil {
return err
}
return nil
}
func runCertUninstall(dir string, scope truststore.Scope, purge bool, store trustStore, out io.Writer) error {
switch err := store.Uninstall(certmanager.CACommonName, scope); {
case errors.Is(err, truststore.ErrUserScopeUnsupported):
if _, e := fmt.Fprintf(out, "%s This platform has no per-user trust store; nothing to remove. Use --system for machine-wide.\n", ui.Colors.Dim("")); e != nil {
return e
}
case err != nil:
return newCertCommandError(errcodes.CertTrustStore, "failed to remove CA from trust store", trustHelp(scope), err)
default:
if _, e := fmt.Fprintf(out, "%s CA removed from %s trust store\n", ui.Colors.Green("✓"), scope.String()); e != nil {
return e
}
}
if purge {
removed := false
for _, p := range []string{certmanager.CACertPath(dir), certmanager.CAKeyPath(dir)} {
if err := os.Remove(p); err != nil {
if os.IsNotExist(err) {
continue
}
return newCertCommandError(errcodes.CertPersistence, "failed to delete CA file",
"Check filesystem permissions", err)
}
removed = true
}
if removed {
if _, err := fmt.Fprintf(out, "%s CA keypair deleted from disk\n", ui.Colors.Green("✓")); err != nil {
return err
}
}
}
return nil
}
func runCertStatus(dir string, store trustStore, out io.Writer) error {
st, err := certmanager.InspectCA(dir)
if err != nil {
return newCertCommandError(errcodes.CertPersistence, "failed to inspect CA",
"The CA file may be corrupt; re-run `pmg setup cert install`", err)
}
user, system, _ := store.Status(certmanager.CACommonName)
st.UserTrusted, st.SystemTrusted = user, system
entries := map[string]string{
"Key Present": strconv.FormatBool(st.KeyPresent),
"Cert Present": strconv.FormatBool(st.CertPresent),
"Trusted (user)": strconv.FormatBool(st.UserTrusted),
"Trusted (system)": strconv.FormatBool(st.SystemTrusted),
}
if st.CertPresent {
entries["Expires"] = st.NotAfter.Format(time.RFC3339)
entries["Fingerprint"] = st.Fingerprint
}
ui.PrintInfoSection("PMG CA Certificate", entries)
drift, reason := st.Drift()
switch {
case drift:
if _, err := fmt.Fprintf(out, "\n%s %s\n", ui.Colors.Red("drift:"), reason); err != nil {
return err
}
case st.ExpiringSoon:
if _, err := fmt.Fprintf(out, "\n%s CA expires within 30 days (%s). Run `pmg setup cert install --force` to rotate.\n",
ui.Colors.Yellow("⚠"), st.NotAfter.Format("2006-01-02")); err != nil {
return err
}
case st.KeyPresent && st.CertPresent && !st.Trusted():
// "not trusted" is expected on Linux (Go honors SSL_CERT_FILE) but a
// real problem where a per-user store exists (macOS/Windows).
if store.UserScopeSupported() {
if _, err := fmt.Fprintf(out, "\n%s CA on disk but not trusted in the OS store. Run `pmg setup cert install`.\n", ui.Colors.Yellow("⚠")); err != nil {
return err
}
} else {
if _, err := fmt.Fprintf(out, "\n%s CA on disk; not in OS store. Expected on Linux (Go honors SSL_CERT_FILE); use --system for store trust.\n", ui.Colors.Dim("")); err != nil {
return err
}
}
}
return nil
}
func otherScope(s truststore.Scope) truststore.Scope {
if s == truststore.ScopeSystem {
return truststore.ScopeUser
}
return truststore.ScopeSystem
}
var geteuid = os.Geteuid
func errIfRunningUnderSudo() error {
if geteuid() == 0 && os.Getenv("SUDO_USER") != "" {
return newCertCommandError(errcodes.PermissionDenied,
"run `pmg setup cert` as your normal user, not with sudo",
"PMG generates a per-user CA keypair and elevates only the system trust step. Re-run without sudo (use --system for machine-wide trust).",
errors.New("invoked under sudo"))
}
return nil
}
func trustHelp(scope truststore.Scope) string {
if scope == truststore.ScopeSystem {
return "Approve the elevation prompt when asked (macOS/Linux), or run from an elevated prompt (Windows)"
}
return "Approve the keychain prompt if shown; on Linux use --system (no per-user store)"
}
+150
View File
@@ -0,0 +1,150 @@
package setup
import (
"bytes"
"os"
"testing"
"github.com/safedep/pmg/proxy/certmanager"
"github.com/safedep/pmg/truststore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type fakeStore struct {
installed bool
installErr error
uninstalled bool
user, system bool
userSupported bool
}
func (f *fakeStore) Install(_ []byte, _ truststore.Scope) error {
if f.installErr != nil {
return f.installErr
}
f.installed = true
return nil
}
func (f *fakeStore) Uninstall(_ string, _ truststore.Scope) error { f.uninstalled = true; return nil }
func (f *fakeStore) Status(_ string) (bool, bool, error) { return f.user, f.system, nil }
func (f *fakeStore) UserScopeSupported() bool { return f.userSupported }
func TestCertInstallGeneratesSavesAndInstalls(t *testing.T) {
dir := t.TempDir()
store := &fakeStore{userSupported: true}
var out bytes.Buffer
require.NoError(t, runCertInstall(dir, truststore.ScopeUser, false, store, &out))
assert.True(t, store.installed)
_, err := certmanager.LoadCA(dir)
require.NoError(t, err) // keypair persisted
}
func TestCertInstallIdempotentWhenAlreadyTrusted(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
store := &fakeStore{user: true, userSupported: true}
var out bytes.Buffer
require.NoError(t, runCertInstall(dir, truststore.ScopeUser, false, store, &out))
assert.False(t, store.installed) // already trusted → no re-install
assert.Contains(t, out.String(), "already installed")
}
func TestCertInstallLinuxUserNoopIsFriendly(t *testing.T) {
dir := t.TempDir()
store := &fakeStore{installErr: truststore.ErrUserScopeUnsupported, userSupported: false}
var out bytes.Buffer
require.NoError(t, runCertInstall(dir, truststore.ScopeUser, false, store, &out))
assert.Contains(t, out.String(), "--system")
}
func TestCertInstallForceRotates(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
store := &fakeStore{userSupported: true}
var out bytes.Buffer
require.NoError(t, runCertInstall(dir, truststore.ScopeUser, true, store, &out))
assert.True(t, store.uninstalled) // rotation uninstalls old first
assert.True(t, store.installed)
}
func TestErrIfRunningUnderSudo(t *testing.T) {
orig := geteuid
t.Cleanup(func() { geteuid = orig })
// root + SUDO_USER set → refused (sudo from a normal user).
geteuid = func() int { return 0 }
t.Setenv("SUDO_USER", "alice")
assert.Error(t, errIfRunningUnderSudo())
// root without SUDO_USER → genuine root, allowed.
t.Setenv("SUDO_USER", "")
assert.NoError(t, errIfRunningUnderSudo())
// non-root → allowed even if SUDO_USER somehow set.
geteuid = func() int { return 1000 }
t.Setenv("SUDO_USER", "alice")
assert.NoError(t, errIfRunningUnderSudo())
}
func TestCertInstallReplacesCorruptedCA(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
// Simulate a partial/corrupt state: cert still on disk (and possibly trusted)
// but the private key is gone. Install must clean up the old root, not stack a
// second one alongside a freshly generated keypair.
require.NoError(t, os.Remove(certmanager.CAKeyPath(dir)))
store := &fakeStore{userSupported: true}
var out bytes.Buffer
require.NoError(t, runCertInstall(dir, truststore.ScopeUser, false, store, &out))
assert.True(t, store.uninstalled, "old trusted root should be cleaned up")
assert.True(t, store.installed)
_, err = certmanager.LoadCA(dir)
require.NoError(t, err) // a complete keypair is regenerated
}
func TestCertUninstallPurgeDeletesFiles(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
store := &fakeStore{}
var out bytes.Buffer
require.NoError(t, runCertUninstall(dir, truststore.ScopeUser, true, store, &out))
assert.True(t, store.uninstalled)
_, err = certmanager.LoadCA(dir)
assert.Error(t, err) // files gone
assert.NoFileExists(t, certmanager.CAKeyPath(dir))
}
func TestCertStatusReportsDrift(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
// Simulate drift: remove the key, keep the cert.
require.NoError(t, os.Remove(certmanager.CAKeyPath(dir)))
store := &fakeStore{}
var out bytes.Buffer
require.NoError(t, runCertStatus(dir, store, &out))
assert.Contains(t, out.String(), "drift")
}
+47
View File
@@ -12,7 +12,9 @@ import (
"github.com/safedep/pmg/internal/shim"
"github.com/safedep/pmg/internal/ui"
"github.com/safedep/pmg/internal/version"
"github.com/safedep/pmg/proxy/certmanager"
"github.com/safedep/pmg/sandbox/platform"
"github.com/safedep/pmg/truststore"
"github.com/spf13/cobra"
)
@@ -28,6 +30,7 @@ const (
checkSandbox = "sandbox"
checkProtectionNpm = "protection-npm"
checkProtectionPip = "protection-pip"
checkCA = "ca-cert"
)
func NewDoctorCommand() *cobra.Command {
@@ -260,6 +263,14 @@ func runCoreChecks(cfg *config.RuntimeConfig) []doctor.CheckResult {
}
},
},
{
Name: checkCA,
Category: "Security",
Run: func() doctor.CheckResult {
user, system, _ := truststore.Status(certmanager.CACommonName)
return evaluateCACheck(cfg.ConfigDir(), user, system, truststore.UserScopeSupported())
},
},
}
return doctor.RunChecks(checks)
}
@@ -317,6 +328,7 @@ var checkDisplayNames = map[string]string{
checkSandbox: "Sandbox",
checkProtectionNpm: "npm protection",
checkProtectionPip: "pip protection",
checkCA: "MITM CA",
}
var checkFixes = map[string]string{
@@ -331,6 +343,41 @@ var checkFixes = map[string]string{
checkEventLogging: "Set skip_event_logging: false in config",
checkProtectionNpm: "pmg setup install",
checkProtectionPip: "pmg setup install",
checkCA: "pmg setup cert install",
}
// evaluateCACheck is the testable core of the CA doctor check. Trust booleans
// and userScopeSupported are injected so the check is hermetic; the live check
// fills them from truststore.
func evaluateCACheck(dir string, userTrusted, systemTrusted, userScopeSupported bool) doctor.CheckResult {
st, err := certmanager.InspectCA(dir)
if err != nil {
return doctor.CheckResult{Status: doctor.StatusFail, Message: "CA files present but unreadable"}
}
if !st.KeyPresent && !st.CertPresent {
return doctor.CheckResult{Status: doctor.StatusWarn, Message: "MITM CA not installed (optional; run pmg setup cert install)"}
}
st.UserTrusted, st.SystemTrusted = userTrusted, systemTrusted
if drift, reason := st.Drift(); drift {
return doctor.CheckResult{Status: doctor.StatusFail, Message: reason}
}
if st.Trusted() {
if st.ExpiringSoon {
return doctor.CheckResult{Status: doctor.StatusWarn, Message: "CA trusted but expiring within 30 days"}
}
return doctor.CheckResult{Status: doctor.StatusPass, Message: "MITM CA installed and trusted"}
}
// On disk but not trusted in any store.
if !userScopeSupported {
// Linux: env-var injection (SSL_CERT_FILE) already covers Go; store trust optional.
return doctor.CheckResult{Status: doctor.StatusWarn, Message: "CA on disk; not in OS store (Linux uses SSL_CERT_FILE; --system for store trust)"}
}
return doctor.CheckResult{Status: doctor.StatusFail, Message: "CA on disk but not trusted; run pmg setup cert install"}
}
func printResults(results []doctor.CheckResult) {
+45
View File
@@ -0,0 +1,45 @@
package setup
import (
"testing"
"github.com/safedep/pmg/internal/doctor"
"github.com/safedep/pmg/proxy/certmanager"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCACheckAbsentIsWarn(t *testing.T) {
res := evaluateCACheck(t.TempDir(), false, false, true)
assert.Equal(t, doctor.StatusWarn, res.Status)
}
func TestCACheckTrustedIsPass(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
res := evaluateCACheck(dir, true, false, true)
assert.Equal(t, doctor.StatusPass, res.Status)
}
func TestCACheckOnDiskNotTrustedMacWinIsFail(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
res := evaluateCACheck(dir, false, false, true) // userScopeSupported=true (mac/win)
assert.Equal(t, doctor.StatusFail, res.Status)
}
func TestCACheckOnDiskNotTrustedLinuxIsWarn(t *testing.T) {
dir := t.TempDir()
ca, err := certmanager.GenerateCA(certmanager.PersistentCACertManagerConfig())
require.NoError(t, err)
require.NoError(t, certmanager.SaveCA(dir, ca))
res := evaluateCACheck(dir, false, false, false) // userScopeSupported=false (linux)
assert.Equal(t, doctor.StatusWarn, res.Status)
}
+22
View File
@@ -15,7 +15,9 @@ import (
"github.com/safedep/pmg/internal/audit"
"github.com/safedep/pmg/internal/ui"
"github.com/safedep/pmg/internal/version"
"github.com/safedep/pmg/proxy/certmanager"
"github.com/safedep/pmg/sandbox/platform"
"github.com/safedep/pmg/truststore"
"github.com/spf13/cobra"
)
@@ -143,6 +145,26 @@ func executeSetupInfo() error {
ui.PrintInfoSection("Sandbox", sandboxEntries)
// Certificate Authority section
caStatus, _ := certmanager.InspectCA(cfg.ConfigDir())
caUser, caSystem, _ := truststore.Status(certmanager.CACommonName)
caStatus.UserTrusted, caStatus.SystemTrusted = caUser, caSystem
caEntries := make(map[string]string)
caEntries["Installed"] = strconv.FormatBool(caStatus.KeyPresent && caStatus.CertPresent)
caScope := "none"
if caStatus.SystemTrusted {
caScope = "system"
} else if caStatus.UserTrusted {
caScope = "user"
}
caEntries["Trust Scope"] = caScope
if caStatus.CertPresent {
caEntries["Expires"] = caStatus.NotAfter.Format("2006-01-02")
caEntries["Fingerprint"] = caStatus.Fingerprint
}
ui.PrintInfoSection("Certificate Authority", caEntries)
if cfg.Config.Cloud.Enabled {
cloudEntries := make(map[string]string)
cloudEntries["Enabled"] = "true"
+1
View File
@@ -28,6 +28,7 @@ func NewSetupCommand() *cobra.Command {
setupCmd.AddCommand(NewRemoveCommand())
setupCmd.AddCommand(NewInfoCommand())
setupCmd.AddCommand(NewDoctorCommand())
setupCmd.AddCommand(NewCertCommand())
return setupCmd
}