Skip to content

Commit

Permalink
Agent can now add and remove trusted CA certs on ubuntu
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Fuerth and Nader Ziada committed May 20, 2015
1 parent 3dad053 commit b2b4544
Show file tree
Hide file tree
Showing 21 changed files with 611 additions and 6 deletions.
6 changes: 4 additions & 2 deletions agent/action/concrete_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func NewFactory(
copier := platform.GetCopier()
dirProvider := platform.GetDirProvider()
vitalsService := platform.GetVitalsService()
certManager := platform.GetCertManager()
ntpService := boshntp.NewConcreteService(platform.GetFs(), dirProvider)

factory = concreteFactory{
Expand All @@ -47,8 +48,9 @@ func NewFactory(
"cancel_task": NewCancelTask(taskService),

// VM admin
"ssh": NewSSH(settingsService, platform, dirProvider),
"fetch_logs": NewFetchLogs(compressor, copier, blobstore, dirProvider),
"ssh": NewSSH(settingsService, platform, dirProvider),
"fetch_logs": NewFetchLogs(compressor, copier, blobstore, dirProvider),
"update_settings": NewUpdateSettings(certManager, logger),

// Job management
"prepare": NewPrepare(applier),
Expand Down
48 changes: 48 additions & 0 deletions agent/action/update_settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package action

import (
"errors"

"github.com/cloudfoundry/bosh-agent/platform/cert"
boshsettings "github.com/cloudfoundry/bosh-agent/settings"
"github.com/cloudfoundry/bosh-utils/logger"
)

type UpdateSettingsAction struct {
trustedCertManager cert.Manager
logger logger.Logger
}

func NewUpdateSettings(trustedCertManager cert.Manager, logger logger.Logger) UpdateSettingsAction {
return UpdateSettingsAction{
trustedCertManager: trustedCertManager,
logger: logger,
}
}

func (a UpdateSettingsAction) IsAsynchronous() bool {
return true
}

func (a UpdateSettingsAction) IsPersistent() bool {
return false
}

func (a UpdateSettingsAction) Run(newSettings boshsettings.Settings) (string, error) {
a.logger.Info("update-settings-action", "Running Update Settings command")

err := a.trustedCertManager.UpdateCertificates(newSettings.Cert)
if err != nil {
return "", err
}

return "updated", nil
}

func (a UpdateSettingsAction) Resume() (interface{}, error) {
return nil, errors.New("not supported")
}

func (a UpdateSettingsAction) Cancel() error {
return errors.New("not supported")
}
57 changes: 57 additions & 0 deletions agent/action/update_settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package action_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"errors"

"github.com/cloudfoundry/bosh-agent/agent/action"
"github.com/cloudfoundry/bosh-agent/platform/cert/fakes"
boshsettings "github.com/cloudfoundry/bosh-agent/settings"
"github.com/cloudfoundry/bosh-utils/logger"
)

func init() {
Describe("UpdateSettings", func() {
var updateAction action.UpdateSettingsAction
var certManager *fakes.FakeManager
var log logger.Logger
BeforeEach(func() {
log = logger.NewLogger(logger.LevelNone)
certManager = new(fakes.FakeManager)
updateAction = action.NewUpdateSettings(certManager, log)
})

It("is synchronous", func() {
Expect(updateAction.IsAsynchronous()).To(BeTrue())
})

It("is not persistent", func() {
Expect(updateAction.IsPersistent()).To(BeFalse())
})

It("returns 'updated' on success", func() {
newSettings := boshsettings.Settings{}
result, err := updateAction.Run(newSettings)
Expect(err).ToNot(HaveOccurred())
Expect(result).To(Equal("updated"))
})

Context("When updating the certificates fails", func() {
BeforeEach(func() {
log = logger.NewLogger(logger.LevelNone)
certManager = new(fakes.FakeManager)
certManager.UpdateCertificatesReturns(errors.New("Error"))
updateAction = action.NewUpdateSettings(certManager, log)
})

It("returns the error", func() {
newSettings := boshsettings.Settings{}
result, err := updateAction.Run(newSettings)
Expect(err).To(HaveOccurred())
Expect(result).To(BeEmpty())
})
})
})
}
4 changes: 4 additions & 0 deletions agent/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

boshplatform "github.com/cloudfoundry/bosh-agent/platform"
boshcdrom "github.com/cloudfoundry/bosh-agent/platform/cdrom"
boshcert "github.com/cloudfoundry/bosh-agent/platform/cert"
boshdisk "github.com/cloudfoundry/bosh-agent/platform/disk"
boshnet "github.com/cloudfoundry/bosh-agent/platform/net"
bosharp "github.com/cloudfoundry/bosh-agent/platform/net/arp"
Expand Down Expand Up @@ -361,6 +362,8 @@ func init() {

ubuntuNetManager := boshnet.NewUbuntuNetManager(fs, runner, ipResolver, interfaceConfigurationCreator, arping, logger)

ubuntuCertManager := boshcert.NewUbuntuCertManager(fs, runner, logger)

monitRetryable := boshplatform.NewMonitRetryable(runner)
monitRetryStrategy := boshretry.NewAttemptRetryStrategy(10, 1*time.Second, monitRetryable, logger)

Expand All @@ -380,6 +383,7 @@ func init() {
linuxCdutil,
diskManager,
ubuntuNetManager,
ubuntuCertManager,
monitRetryStrategy,
devicePathResolver,
500*time.Millisecond,
Expand Down
3 changes: 2 additions & 1 deletion bin/golint
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ errors=$(
| grep -v '/mocks/' \
| grep -v 'should not be capitalized' \
| grep -v 'underscore in package name' \
| grep -v 'bootstrapper/spec/'
| grep -v 'bootstrapper/spec/' \
| grep -v 'platform/cert/fakes/fake_manager.go'
)

if [ "$(echo -n "$errors")" != "" ]; then
Expand Down
1 change: 1 addition & 0 deletions platform/cdrom/linux_cdrom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cdrom_test

import (
"errors"

. "github.com/cloudfoundry/bosh-agent/platform/cdrom"
fakeudev "github.com/cloudfoundry/bosh-agent/platform/udevdevice/fakes"
fakesys "github.com/cloudfoundry/bosh-utils/system/fakes"
Expand Down
116 changes: 116 additions & 0 deletions platform/cert/cert_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package cert

import (
"fmt"
"os"
"strings"

"github.com/cloudfoundry/bosh-utils/logger"
boshsys "github.com/cloudfoundry/bosh-utils/system"
)

//go:generate counterfeiter . Manager

// Manager is a set of operations for manipulating the set of trusted CA certificates
// on any OS platform.
type Manager interface {

// UpdateCertificates manages the set of CA certificates that are trusted in
// addition to certificates that were pre-installed on the operating system.
//
// Each call alters the set of X.509 certificates that are trusted as
// root certificates on this machine to match the set of certificates given.
//
// Calling this method again later with a different set of certificates will
// replace the previously trusted certificates with the new set; hence, calling
// this method with an empty set of certificates will bring this machine back to
// the initial state, where it only trusts the CA certificates that came with the OS.
//
// The certs argument should contain zero or more X.509 certificates in PEM format
// concatenated together. Any text that is not between `-----BEGIN CERTIFICATE-----`
// and `-----END CERTIFICATE-----` lines is ignored.
UpdateCertificates(certs string) error
}

type certManager struct {
fs boshsys.FileSystem
runner boshsys.CmdRunner
path string
updateCmdPath string
logger logger.Logger
}

func NewUbuntuCertManager(fs boshsys.FileSystem, runner boshsys.CmdRunner, logger logger.Logger) Manager {
return certManager{
fs: fs,
runner: runner,
path: "/usr/local/share/ca-certificates/",
updateCmdPath: "/usr/sbin/update-ca-certificates",
logger: logger,
}
}

func (c certManager) UpdateCertificates(certs string) error {
c.logger.Info("cert-manager", "Running Update Certificate command")

//TODO: should we backup files to be able to restore state?

deletedFilesCount, err := deleteFiles(c.fs, c.path, "bosh-trusted-cert")
c.logger.DebugWithDetails("cert-manager", "Deleted %d existing certificate files", deletedFilesCount)
if err != nil {
return err
}

slicedCerts := splitCerts(certs)
for i, cert := range slicedCerts {
err := c.fs.WriteFileString(fmt.Sprintf("%sbosh-trusted-cert-%d.crt", c.path, i+1), cert)
if err != nil {
return err
}
}
c.logger.DebugWithDetails("cert-manager", "Wrote %d new certificate files", len(slicedCerts))

_, _, exitStatus, err := c.runner.RunCommand(c.updateCmdPath)
if err != nil {
return err
}
if exitStatus != 0 {
return fmt.Errorf("%s failed with exit status %d", c.updateCmdPath, exitStatus)
}
return nil

}

// SplitCerts returns a slice containing each PEM certificate in the given string.
// extra data before the first cert, between each cert, and after the last cert
// is all discarded. Each string in the returned slice will begin with
// `-----BEGIN CERTIFICATE-----` and end with `-----END CERTIFICATE-----`
// and have no leading or trailing whitespace.
func splitCerts(certs string) []string {
result := strings.SplitAfter(fmt.Sprintln(certs), "-----END CERTIFICATE-----")
for i := range result {
start := strings.Index(result[i], "-----BEGIN CERTIFICATE-----")
if start > 0 {
result[i] = result[i][start:len(result[i])]
}
}
return result[0 : len(result)-1]
}

func deleteFiles(fs boshsys.FileSystem, path string, filenamePrefix string) (int, error) {
var deletedFilesCount int
fullyQualifiedPrefix := fmt.Sprintf("%s%s", path, filenamePrefix)
err := fs.Walk(path, func(fname string, info os.FileInfo, walkError error) (err error) {
if walkError != nil {
return walkError
}
if strings.HasPrefix(fname, fullyQualifiedPrefix) {
err = fs.RemoveAll(fname)
if err == nil {
deletedFilesCount++
}
}
return err
})
return deletedFilesCount, err
}
Loading

0 comments on commit b2b4544

Please sign in to comment.