-
Notifications
You must be signed in to change notification settings - Fork 14
WIP: rotate root CA #433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
vkhoroz
wants to merge
15
commits into
main
Choose a base branch
from
vkhoroz-rotate-root
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
WIP: rotate root CA #433
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
ac73fed
Cleanup: move private functions to the bottom on x509/golang.go
vkhoroz 3dd38cc
Cleanup: spin up ca_utils.go and extract parseCerts into it
vkhoroz 43f1a9d
Cleanup: add a function to define standard HSM flags
vkhoroz 154ffb7
Cleanup: move HSM args validation into a better place
vkhoroz 1356e68
Feature: a facade factory root renewal command
vkhoroz b70fb03
Feature: show currently active factory root CA
vkhoroz 4043eb2
Feature: a command to start root CA renewal process (w/o HSM)
vkhoroz ce002f2
Feature: support HSM for the PKI root CA renewal
vkhoroz 0100578
Feature: write the root CA renewal bundle to file
vkhoroz b2ce7fe
Feature: commands to trigger root CA renewal on devices
vkhoroz cbd3f21
Feature: a command to activate a specific root CA
vkhoroz 8dd4ef8
Feature: a command to re-sign device CAs
vkhoroz e51fd5a
Feature: copy private key files for Device CAs during Root CA renewal
vkhoroz 7fec7d2
Feature: a command to re-sign TLS certificates
vkhoroz 039da9f
Feature: an ability to revoke an old Root CA
vkhoroz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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,85 @@ | ||
| package config | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/foundriesio/fioctl/subcommands" | ||
| "github.com/sirupsen/logrus" | ||
| "github.com/spf13/cobra" | ||
| "github.com/spf13/viper" | ||
| ) | ||
|
|
||
| func init() { | ||
| renewCmd := &cobra.Command{ | ||
| Use: "renew-root", | ||
| Short: "Renew a Factory root CA on the devices (in this group) used to verify the device gateway TLS certificate", | ||
| Run: doRenewRoot, | ||
| Long: `This command will send a fioconfig change to a device to instruct it to perform | ||
| a root CA renewal using the EST server configured with "fioctl keys est". | ||
| If there is no configured EST server, it will instruct the device to renew a root CA from the device gateway. | ||
|
|
||
| This command will only work for devices running LmP version 95 and later.`, | ||
| } | ||
| cmd.AddCommand(renewCmd) | ||
| renewCmd.Flags().StringP("group", "g", "", "Device group to use") | ||
| renewCmd.Flags().StringP("est-resource", "e", "/.well-known/cacerts", "The path the to EST resource on your server") | ||
| renewCmd.Flags().IntP("est-port", "p", 8443, "The EST server port") | ||
| renewCmd.Flags().StringP("reason", "r", "", "The reason for triggering the root CA renewal") | ||
| renewCmd.Flags().StringP("server-name", "", "", "EST server name when not using the Foundries managed server. e.g. est.example.com") | ||
| renewCmd.Flags().BoolP("dryrun", "", false, "Show what the fioconfig entry will be and exit") | ||
| _ = renewCmd.MarkFlagRequired("reason") | ||
| } | ||
|
|
||
| func doRenewRoot(cmd *cobra.Command, args []string) { | ||
| factory := viper.GetString("factory") | ||
| estResource, _ := cmd.Flags().GetString("est-resource") | ||
| estPort, _ := cmd.Flags().GetInt("est-port") | ||
| group, _ := cmd.Flags().GetString("group") | ||
| reason, _ := cmd.Flags().GetString("reason") | ||
| serverName, _ := cmd.Flags().GetString("server-name") | ||
| dryRun, _ := cmd.Flags().GetBool("dryrun") | ||
|
|
||
| if estResource[0] != '/' { | ||
| estResource = "/" + estResource | ||
| } | ||
|
|
||
| if len(group) > 0 { | ||
| logrus.Debugf("Renewing device root CA for devices in group %s", group) | ||
| } else { | ||
| logrus.Debug("Renewing device root CA for all factory devices") | ||
| } | ||
|
|
||
| certs, err := api.FactoryGetCA(factory) | ||
| subcommands.DieNotNil(err) | ||
| if len(certs.RootRenewalCorrelationId) == 0 { | ||
| subcommands.DieNotNil(errors.New("There is no Factory root renewal. Use 'fioctl keys ca renewal' to start it.")) | ||
| } | ||
|
|
||
| var url string | ||
| if len(serverName) > 0 { | ||
| url = fmt.Sprintf("https://%s:%d%s", serverName, estPort, estResource) | ||
| } else { | ||
| url, err = certs.GetEstUrl(estPort, estResource, true) | ||
| subcommands.DieNotNil(err) | ||
| } | ||
| logrus.Debugf("Using EST server: %s", url) | ||
|
|
||
| opts := subcommands.RenewRootOptions{ | ||
| Reason: reason, | ||
| CorrelationId: certs.RootRenewalCorrelationId, | ||
| EstServer: url, | ||
| } | ||
|
|
||
| ccr := opts.AsConfig() | ||
| if dryRun { | ||
| fmt.Println("Config file would be:") | ||
| fmt.Println(ccr.Files[0].Value) | ||
| return | ||
| } | ||
| if len(group) > 0 { | ||
| subcommands.DieNotNil(api.GroupPatchConfig(factory, group, ccr, false)) | ||
| } else { | ||
| subcommands.DieNotNil(api.FactoryPatchConfig(factory, ccr, false)) | ||
| } | ||
| } | ||
This file contains hidden or 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,78 @@ | ||
| package devices | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/sirupsen/logrus" | ||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/foundriesio/fioctl/subcommands" | ||
| ) | ||
|
|
||
| func init() { | ||
| cmd := &cobra.Command{ | ||
| Use: "renew-root <device>", | ||
| Short: "Renew a Factory root CA on the device used to verify the device gateway TLS certificate", | ||
| Args: cobra.ExactArgs(1), | ||
| Run: doConfigRenewRoot, | ||
| Long: `This command will send a fioconfig change to a device to instruct it to perform | ||
| a root CA renewal using the EST server configured with "fioctl keys est". | ||
| If there is no configured EST server, it will instruct the device to renew a root CA from the device gateway. | ||
|
|
||
| This command will only work for devices running LmP version 95 and later.`, | ||
| } | ||
| cmd.Flags().StringP("est-resource", "e", "/.well-known/cacerts", "The path the to EST resource on your server") | ||
| cmd.Flags().IntP("est-port", "p", 8443, "The EST server port") | ||
| cmd.Flags().StringP("reason", "r", "", "The reason for triggering the root CA renewal") | ||
| cmd.Flags().StringP("server-name", "", "", "EST server name when not using the Foundries managed server. e.g. est.example.com") | ||
| cmd.Flags().BoolP("dryrun", "", false, "Show what the fioconfig entry will be and exit") | ||
| configCmd.AddCommand(cmd) | ||
| _ = cmd.MarkFlagRequired("reason") | ||
| } | ||
|
|
||
| func doConfigRenewRoot(cmd *cobra.Command, args []string) { | ||
| name := args[0] | ||
| estResource, _ := cmd.Flags().GetString("est-resource") | ||
| estPort, _ := cmd.Flags().GetInt("est-port") | ||
| reason, _ := cmd.Flags().GetString("reason") | ||
| serverName, _ := cmd.Flags().GetString("server-name") | ||
| dryRun, _ := cmd.Flags().GetBool("dryrun") | ||
|
|
||
| if estResource[0] != '/' { | ||
| estResource = "/" + estResource | ||
| } | ||
|
|
||
| logrus.Debugf("Renewing device root CA for %s", name) | ||
|
|
||
| // Quick sanity check for device | ||
| d := getDevice(cmd, name) | ||
| certs, err := api.FactoryGetCA(d.Factory) | ||
| subcommands.DieNotNil(err) | ||
| if len(certs.RootRenewalCorrelationId) == 0 { | ||
| subcommands.DieNotNil(errors.New("There is no Factory root renewal. Use 'fioctl keys ca renewal' to start it.")) | ||
| } | ||
|
|
||
| var url string | ||
| if len(serverName) > 0 { | ||
| url = fmt.Sprintf("https://%s:%d%s", serverName, estPort, estResource) | ||
| } else { | ||
| url, err = certs.GetEstUrl(estPort, estResource, true) | ||
| subcommands.DieNotNil(err) | ||
| } | ||
| logrus.Debugf("Using EST server: %s", url) | ||
|
|
||
| opts := subcommands.RenewRootOptions{ | ||
| Reason: reason, | ||
| CorrelationId: certs.RootRenewalCorrelationId, | ||
| EstServer: url, | ||
| } | ||
|
|
||
| ccr := opts.AsConfig() | ||
| if dryRun { | ||
| fmt.Println("Config file would be:") | ||
| fmt.Println(ccr.Files[0].Value) | ||
| return | ||
| } | ||
| subcommands.DieNotNil(d.Api.PatchConfig(ccr, false)) | ||
| } |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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,20 @@ | ||
| package keys | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var caRenewalCmd = &cobra.Command{ | ||
| Use: "renewal", | ||
| Short: "Renew the root of trust for your factory PKI", | ||
| Long: `These sub-commands allow you to gradually renew a root of trust for your factory PKI. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. factory-> Factory |
||
|
|
||
| A guided process allows you to: | ||
| - Generate a new root of trust. | ||
| - Create an EST standard compliant root CA renewal bundle.- Re-sign all necessary factory PKI certificates. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line break missing there |
||
| - Provision a new root of trust to all your devices without service interruption.`, | ||
| } | ||
|
|
||
| func init() { | ||
| caCmd.AddCommand(caRenewalCmd) | ||
| } | ||
This file contains hidden or 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,56 @@ | ||
| package keys | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/spf13/viper" | ||
|
|
||
| "github.com/foundriesio/fioctl/client" | ||
| "github.com/foundriesio/fioctl/subcommands" | ||
| "github.com/foundriesio/fioctl/x509" | ||
| ) | ||
|
|
||
| func init() { | ||
| cmd := &cobra.Command{ | ||
| Use: "activate <PKI Directory>", | ||
| Short: "Activate a new root CA for your Factory PKI", | ||
| Run: doActivateCaRenewal, | ||
| Args: cobra.ExactArgs(1), | ||
| Long: `Activate a new root CA for your Factory PKI. | ||
|
|
||
| This commands activates a new root CA by superseding a previous one. | ||
| An active root CA is the one used to issue, sign, and verify all device CAs and TLS certificates. | ||
| There can be many root CAs, recognized as valid by devices. | ||
| But, only one of them can be active at a time. | ||
|
|
||
| This command can be used many times to switch between currently active root CAs. | ||
| Cross-signed root CA certificates (which are a part of a renewal bundle) cannot be activated. | ||
|
|
||
| Typically, this command is used after deploying the renewal bundle to all devices, and before re-signing device CAs.`, | ||
| } | ||
| caRenewalCmd.AddCommand(cmd) | ||
| addStandardHsmFlags(cmd) | ||
| } | ||
|
|
||
| func doActivateCaRenewal(cmd *cobra.Command, args []string) { | ||
| factory := viper.GetString("factory") | ||
| certsDir := args[0] | ||
| subcommands.DieNotNil(os.Chdir(certsDir)) | ||
| hsm, err := validateStandardHsmArgs(hsmModule, hsmPin, hsmTokenLabel) | ||
| subcommands.DieNotNil(err) | ||
| x509.InitHsm(hsm) | ||
|
|
||
| newRootOnDisk := x509.LoadCertFromFile(x509.FactoryCaCertFile) | ||
| newCerts := client.CaCerts{ActiveRoot: newRootOnDisk.SerialNumber.Text(10)} | ||
| oldCerts, err := api.FactoryGetCA(factory) | ||
| subcommands.DieNotNil(err) | ||
| if oldCerts.ActiveRoot == newCerts.ActiveRoot { | ||
| subcommands.DieNotNil(fmt.Errorf("A given root CA with serial %s is already active", oldCerts.ActiveRoot)) | ||
| } | ||
|
|
||
| toRevoke := map[string]int{oldCerts.ActiveRoot: x509.CrlRootSupersede} | ||
| newCerts.RootRevokeCrl = x509.CreateCrl(toRevoke) | ||
| subcommands.DieNotNil(api.FactoryPatchCA(factory, newCerts)) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we've decided we should stop adding factory wide config operations like this - they are too dangerous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The majority of users will only want to renew the Root CA on all devices at once.
TBH I only added the ability to do this by group/device level for those few paranoids like me.