Skip to content
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

User provided certs #1690

Open
wants to merge 16 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions cmd/bootstrap/PROVIDED_CERTIFICATES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# User provided certificates

Users can provide their own certificates to be used with Skupper V2 in non kube sites
during the bootstrap of a local site, when preparing a site bundle and even while installing
a site bundle at a remote machine.

## How certificates are used internally

During the initialization of a local non kube site or when a site bundle is being prepared,
Skupper generates an internal Certificate Authority (CA) named `skupper-site-ca`.

The certificates generated by Skupper are stored under the namespace home directory in the
file system, using the following tree:

```shell
certificates/
├── ca
├── client
└── server
```

As the CA `skupper-site-ca` is generated, it will be stored under the `certificates/ca` path
as a directory named `skupper-site-ca`. Inside a certificate folder, the following files are
expected: `tls.key`, `tls.crt` and `ca.crt` (when dealing with a CA, `ca.crt` and `tls.crt`
have the same content).

If a `RouterAccess` is defined as part of the non kube site definition, Skupper will also
generate a server certificate that is valid for the provided `RouterAccess.spec.bindHost` and
for each entry in the `RouterAccess.spec.subjectAlternativeName` list. The respective certificate
is signed by the CA mentioned earlier (skupper-site-ca) and it will be named according to the
provided `spec.tlsCredentials` field, or when it is omitted, the certificate will be named as the
RouterAccess and stored as a directory under `certificates/server`.

A set of static links are also created for the provided `RouterAccess.spec.bindHost` and for each
entry in the `RouterAccess.spec.subjectAlternativeName` list as separate YAML files.

These static links are composed by a `Link` and a `Secret`, which is basically the client certificate
signed by `skupper-site-ca`, that will be used by other sites to establish Skupper links. The client
certificate is also named based on the RouterAccess `spec.tlsCredentials` field or, when it is omitted,
the certificate will be named as the RouterAccess, prefixed with `client-` and stored under
`certificates/client`.

After a site bundle has been produced, it contains the whole site definition that can be installed
at a remote location. Thus, all certificates and static links are already part of the bundle and no
new certificate is signed at the moment a bundle is installed.

## Providing your own certificates

You can provide your own certificates to be used by Skupper for site linking.

The provided certificates must be placed in a similar directory tree, as shown before,
but its path under the namespace home is located at `input/certificates` instead.

Certificates placed under this path will be used by Skupper primarily and
all the other certificates expected by Skupper, will be generated when not provided.

Depending on your goals, some certificates should be supplied at certain phases,
for example, at the time a local site is being initialized, a bundle is being prepared
or a bundle is being installed at a remote location.

To understand it better, let's go through the main use cases and review what is the ideal
phase that a given kind of certificate should be provided.

### Using a custom CA to sign certificates

If you want Skupper to generate client and server certificates signed by a custom CA,
you will need to provide the respective certificates during:
grs marked this conversation as resolved.
Show resolved Hide resolved

* Local site initialization time
* Site bundle preparation time

Certificates are signed by Skupper at the time a local namespace is being initialized
or a bundle is being produced.

After a site bundle has been produced, it already contains the whole definition and no
new certificate is expected to be signed.

### Using custom server and client certificates

If you want a new local site to use your custom server and client certificates, you can
provide them at any time, for example:

* Local site initialization time
* Site bundle preparation time
* Site bundle installation time

During "Local site initialization time" or "Site bundle preparation time", Skupper will detect
grs marked this conversation as resolved.
Show resolved Hide resolved
that a server certificate has been provided and will inspect it to determine its subject
alternative names and based on that, a set of static links will be created, allowing those
links to be distributed to target sites accordingly. For the static links to remain valid,
the expected client certificate must also be provided.

If a server certificate is provided at "Site bundle installation time", Skupper will also try to
determine its subject alternative names using the `openssl` binary (only if available) and it will
use it to generate the static links for the bundle installation. This way, the set of static links
available for an installed bundle will be valid for all expected target hostnames and IP addresses
defined through the server certificate.

Again, if a server certificate is provided, the respective client certificate is also expected
so that the static links have valid client credentials.

## Examples

## Provide your own skupper-site-ca

If you want Skupper to use your own CA certificates to generate and sign server and client
certificates used for site linking, you can simply create the following structure under
the namespace home of your choice, for example:

```shell
${HOME}/.local/share/skupper/namespaces/default/input/certificates/
└── ca
└── skupper-site-ca
grs marked this conversation as resolved.
Show resolved Hide resolved
├── ca.crt
├── tls.crt
└── tls.key
```

With that, if you bootstrap a site to run in the default namespace, the CA certificates above will be
used to sign the server and client certificates for site linking for each provided `RouterAccess`.

Note that if a CA is provided at the time a site bundle is being installed, it will be detected,
but it won't be used unless the respective namespace is re-initialized. That is because when a bundle
is being installed, it will simply copy certificates provided by the user to be used internally, but
no new certificate will be signed during a site bundle installation.

## Server and Client certificates

Server and client certificates can be provided whenever your site definition contains at least
one `RouterAccess` (resource).

The expected directory names for the server and client certificates, is determined based on the
values of `RouterAccess.spec.tlsCredentials` (optional field), or `RouterAccess.name` (default).

Supposing the value of `RouterAccess.spec.tlsCredentials` or `RouterAccess.name` (when the tlsCredentials
field is omitted) is `my-router-access`, then the following structure, for server and client certificates,
must be provided under the namespace home of your choice, for example:

```shell
${HOME}/.local/share/skupper/namespaces/default/input/certificates/
├── client
│ └── client-my-router-access
│ ├── ca.crt
│ ├── tls.crt
│ └── tls.key
└── server
└── my-router-access
├── ca.crt
├── tls.crt
└── tls.key
```

At bootstrap or bundle installation times, you should see a message saying that the
user provided server and client certificates have been found.

As an example, inspecting the subject alternative names of the provided server certificate above,
and supposing it is valid for the following domain name:

```shell
X509v3 Subject Alternative Name:
DNS:my.local.server.com
```

If the following domain name is not defined as being the `spec.bindHost` or as part of the
`spec.subjectAlternativeNames` list of the `RouterAccess` resource, Skupper will also create a static
link that uses `my.local.server.com` as the target endpoint at:

```shell
$HOME/.local/share/skupper/namespaces/default/runtime/link/link-my-router-access-my.local.server.com.yaml
```

If the respective server certificates are defined at bundle installation time, Skupper will also inspect
the subject alternative names of the public server certificate and create the static links for each domain
name and ip address found, only if the `openssl` binary is available.

It is important that the client certificate is also provided, as all static links will be updated
to use the provided client credentials.
10 changes: 9 additions & 1 deletion cmd/bootstrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ If the namespace is omitted, the "default" namespace is used.
./cmd/bootstrap/remove.sh [namespace]
```

## Using custom certificates

Users can provide their own certificates to be used when initializing a local site,
when preparing a site bundle to be installed somewhere else and even during a site
bundle installation time.

More about user provided certificates can be found [here](PROVIDED_CERTIFICATES.md).

## Example

Here is a very basic demonstration on how you can run the `Hello World` example
Expand Down Expand Up @@ -373,7 +381,7 @@ in your browser and it should work.
### Updating an existing installation

Suppose modifications have been made to the `west` site CRs, directly at the
namespace directory (i.e: ${HOME}/.local/share/skupper/namespaces/west/sources).
namespace directory (i.e: ${HOME}/.local/share/skupper/namespaces/west/input/sources).

To re-initialize the west site, run:

Expand Down
31 changes: 18 additions & 13 deletions cmd/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/skupperproject/skupper/api/types"
internalbundle "github.com/skupperproject/skupper/internal/nonkube/bundle"
internalutils "github.com/skupperproject/skupper/internal/utils"
"github.com/skupperproject/skupper/pkg/config"
"github.com/skupperproject/skupper/pkg/nonkube/api"
"github.com/skupperproject/skupper/pkg/nonkube/bundle"
Expand Down Expand Up @@ -80,7 +81,6 @@ func main() {
fmt.Println(version.Version)
os.Exit(0)
}
// if user overrides and force empty, use it as default
if inputPath != "" {
var err error
inputPath, err = filepath.Abs(inputPath)
Expand Down Expand Up @@ -150,16 +150,27 @@ func main() {
if namespace == "" {
namespace = "default"
}
existingPath := api.GetInternalOutputPath(namespace, api.InputSiteStatePath)
inputSourcesDefined := false
if _, err := os.Stat(existingPath); err == nil {
dirReader := new(internalutils.DirectoryReader)
filesFound, _ := dirReader.ReadDir(existingPath, nil)
inputSourcesDefined = len(filesFound) > 0
}
if inputPath == "" {
// when input path is empty, but a namespace is provided, try to reload an existing site definition
existingPath := api.GetInternalOutputPath(namespace, api.LoadedSiteStatePath)
if _, err := os.Stat(existingPath); err == nil {
if inputSourcesDefined {
inputPath = existingPath
fmt.Printf("Sources will consumed from namespace %q\n", namespace)
} else {
fmt.Printf("Input path has not been provided and namespace %s does not exist\n", namespace)
fmt.Printf("No sources found at: %s\n", path.Join(api.GetHostNamespaceHome(namespace), string(api.InputSiteStatePath)))
os.Exit(1)
}
} else if inputSourcesDefined {
fmt.Printf("Input path has been provided, but namespace %s has input sources defined at:\n", namespace)
fmt.Printf("%s\n", path.Join(api.GetHostNamespaceHome(namespace), string(api.InputSiteStatePath)))
os.Exit(1)
}

// if namespace already exists, fail if force is not set
Expand Down Expand Up @@ -195,24 +206,18 @@ func main() {
if !isBundle {
fmt.Printf("Platform: %s\n", platform)
tokenPath := api.GetInternalOutputPath(siteState.Site.Namespace, api.RuntimeTokenPath)
hostTokenPath, err := api.GetHostSiteInternalPath(siteState.Site, api.RuntimeTokenPath)
if err != nil {
fmt.Println("Failed to get site's static links path:", err)
}
hostTokenPath := api.GetHostSiteInternalPath(siteState.Site, api.RuntimeTokenPath)
tokens, _ := os.ReadDir(tokenPath)
for _, token := range tokens {
if !token.IsDir() {
fmt.Println("Static links have been defined at:", hostTokenPath)
break
}
}
sourcesPath, _ := api.GetHostSiteInternalPath(siteState.Site, api.LoadedSiteStatePath)
sourcesPath := api.GetHostSiteInternalPath(siteState.Site, api.InputSiteStatePath)
fmt.Printf("Definition is available at: %s\n", sourcesPath)
} else {
siteHome, err := api.GetHostBundlesPath()
if err != nil {
fmt.Println("Failed to get site bundle base directory:", err)
}
siteHome := api.GetHostBundlesPath()
installationFile := path.Join(siteHome, fmt.Sprintf("skupper-install-%s.sh", siteState.Site.Name))
if internalbundle.GetBundleStrategy(bundleStrategy) == string(internalbundle.BundleStrategyTarball) {
installationFile = path.Join(siteHome, fmt.Sprintf("skupper-install-%s.tar.gz", siteState.Site.Name))
Expand All @@ -227,7 +232,7 @@ func bootstrap(inputPath string, namespace string, bundleStrategy string) (*api.
var siteStateLoader api.SiteStateLoader
var reloadExisting bool
isBundle := bundleStrategy != ""
sourcesPath := api.GetInternalOutputPath(namespace, api.LoadedSiteStatePath)
sourcesPath := api.GetInternalOutputPath(namespace, api.InputSiteStatePath)
_, err := os.Stat(sourcesPath)
if !isBundle && err == nil {
reloadExisting = true
Expand Down
Loading