Merge pull request #1701 from ericrrath/oci-auth-instance-principal

OCI provider: add support for instance principal authentication
This commit is contained in:
Kubernetes Prow Robot 2023-04-11 06:43:06 -07:00 committed by GitHub
commit e7749e94d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 19 deletions

View File

@ -6,16 +6,25 @@ Make sure to use the latest version of ExternalDNS for this tutorial.
## Creating an OCI DNS Zone
Create a DNS zone which will contain the managed DNS records. Let's use `example.com` as an reference here.
Create a DNS zone which will contain the managed DNS records. Let's use
`example.com` as a reference here. Make note of the OCID of the compartment
in which you created the zone; you'll need to provide that later.
For more information about OCI DNS see the documentation [here][1].
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
The OCI provider supports two authentication options: key-based and instance
principals.
### Key-based
We first need to create a config file containing the information needed to connect with the OCI API.
Create a new file (oci.yaml) and modify the contents to match the example below. Be sure to adjust the values to match your own credentials:
Create a new file (oci.yaml) and modify the contents to match the example
below. Be sure to adjust the values to match your own credentials, and the OCID
of the compartment containing the zone:
```yaml
auth:
@ -37,7 +46,29 @@ Create a secret using the config file above:
$ kubectl create secret generic external-dns-config --from-file=oci.yaml
```
### Manifest (for clusters with RBAC enabled)
### OCI IAM Instance Principal
If you're running ExternalDNS within OCI, you can use OCI IAM instance
principals to authenticate with OCI. This obviates the need to create the
secret with your credentials. You'll need to ensure an OCI IAM policy exists
with a statement granting the `manage dns` permission on zones and records in
the target compartment to the dynamic group covering your instance running
ExternalDNS.
E.g.:
```
Allow dynamic-group <dynamic-group-name> to manage dns in compartment id <target-compartment-OCID>
```
You'll also need to add the `--oci-instance-principals=true` flag to enable
this type of authentication. Finally, you'll need to add the
`--oci-compartment-ocid=ocid1.compartment.oc1...` flag to provide the OCID of
the compartment containing the zone to be managed.
For more information about OCI IAM instance principals, see the documentation [here][2].
For more information about OCI IAM policy details for the DNS service, see the documentation [here][3].
## Manifest (for clusters with RBAC enabled)
Apply the following manifest to deploy ExternalDNS.
@ -159,3 +190,6 @@ $ kubectl apply -f nginx.yaml
```
[1]: https://docs.cloud.oracle.com/iaas/Content/DNS/Concepts/dnszonemanagement.htm
[2]: https://docs.cloud.oracle.com/iaas/Content/Identity/Reference/dnspolicyreference.htm
[3]: https://docs.cloud.oracle.com/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm

15
main.go
View File

@ -18,6 +18,7 @@ package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
@ -312,7 +313,19 @@ func main() {
)
case "oci":
var config *oci.OCIConfig
config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
// if the instance-principals flag was set, and a compartment OCID was provided, then ignore the
// OCI config file, and provide a config that uses instance principal authentication.
if cfg.OCIAuthInstancePrincipal {
if len(cfg.OCICompartmentOCID) == 0 {
err = fmt.Errorf("instance principal authentication requested, but no compartment OCID provided")
} else {
authConfig := oci.OCIAuthConfig{UseInstancePrincipal: true}
config = &oci.OCIConfig{Auth: authConfig, CompartmentID: cfg.OCICompartmentOCID}
}
} else {
config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
}
if err == nil {
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
}

View File

@ -131,6 +131,8 @@ type Config struct {
DynPassword string `secure:"yes"`
DynMinTTLSeconds int
OCIConfigFile string
OCICompartmentOCID string
OCIAuthInstancePrincipal bool
InMemoryZones []string
OVHEndpoint string
OVHApiRateLimit int
@ -498,6 +500,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("dyn-password", "When using the Dyn provider, specify the password").Default("").StringVar(&cfg.DynPassword)
app.Flag("dyn-min-ttl", "Minimal TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is lower than this.").IntVar(&cfg.DynMinTTLSeconds)
app.Flag("oci-config-file", "When using the OCI provider, specify the OCI configuration file (required when --provider=oci").Default(defaultConfig.OCIConfigFile).StringVar(&cfg.OCIConfigFile)
app.Flag("oci-compartment-ocid", "When using the OCI provider, specify the OCID of the OCI compartment containing all managed zones and records. Required when using OCI IAM instance principal authentication.").StringVar(&cfg.OCICompartmentOCID)
app.Flag("oci-auth-instance-principal", "When using the OCI provider, specify whether OCI IAM instance principal authentication should be used (instead of key-based auth via the OCI config file).").Default(strconv.FormatBool(defaultConfig.OCIAuthInstancePrincipal)).BoolVar(&cfg.OCIAuthInstancePrincipal)
app.Flag("rcodezero-txt-encrypt", "When using the Rcodezero provider with txt registry option, set if TXT rrs are encrypted (default: false)").Default(strconv.FormatBool(defaultConfig.RcodezeroTXTEncrypt)).BoolVar(&cfg.RcodezeroTXTEncrypt)
app.Flag("inmemory-zone", "Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.InMemoryZones)
app.Flag("ovh-endpoint", "When using the OVH provider, specify the endpoint (default: ovh-eu)").Default(defaultConfig.OVHEndpoint).StringVar(&cfg.OVHEndpoint)

View File

@ -22,6 +22,7 @@ import (
"strings"
"github.com/oracle/oci-go-sdk/common"
"github.com/oracle/oci-go-sdk/common/auth"
"github.com/oracle/oci-go-sdk/dns"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@ -36,12 +37,13 @@ const ociRecordTTL = 300
// OCIAuthConfig holds connection parameters for the OCI API.
type OCIAuthConfig struct {
Region string `yaml:"region"`
TenancyID string `yaml:"tenancy"`
UserID string `yaml:"user"`
PrivateKey string `yaml:"key"`
Fingerprint string `yaml:"fingerprint"`
Passphrase string `yaml:"passphrase"`
Region string `yaml:"region"`
TenancyID string `yaml:"tenancy"`
UserID string `yaml:"user"`
PrivateKey string `yaml:"key"`
Fingerprint string `yaml:"fingerprint"`
Passphrase string `yaml:"passphrase"`
UseInstancePrincipal bool `yaml:"useInstancePrincipal"`
}
// OCIConfig holds the configuration for the OCI Provider.
@ -87,14 +89,25 @@ func LoadOCIConfig(path string) (*OCIConfig, error) {
// NewOCIProvider initializes a new OCI DNS based Provider.
func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
var client ociDNSClient
client, err := dns.NewDnsClientWithConfigurationProvider(common.NewRawConfigurationProvider(
cfg.Auth.TenancyID,
cfg.Auth.UserID,
cfg.Auth.Region,
cfg.Auth.Fingerprint,
cfg.Auth.PrivateKey,
&cfg.Auth.Passphrase,
))
var err error
var configProvider common.ConfigurationProvider
if cfg.Auth.UseInstancePrincipal {
configProvider, err = auth.InstancePrincipalConfigurationProvider()
if err != nil {
return nil, errors.Wrap(err, "error creating OCI instance principal config provider")
}
} else {
configProvider = common.NewRawConfigurationProvider(
cfg.Auth.TenancyID,
cfg.Auth.UserID,
cfg.Auth.Region,
cfg.Auth.Fingerprint,
cfg.Auth.PrivateKey,
&cfg.Auth.Passphrase,
)
}
client, err = dns.NewDnsClientWithConfigurationProvider(configProvider)
if err != nil {
return nil, errors.Wrap(err, "initializing OCI DNS API client")
}

View File

@ -19,6 +19,7 @@ package oci
import (
"context"
"sort"
"strings"
"testing"
"github.com/oracle/oci-go-sdk/common"
@ -167,6 +168,17 @@ hKRtDhmSdWBo3tJK12RrAe4t7CUe8gMgTvU7ExlcA3xQkseFPx9K
},
},
},
"instance-principal": {
// testing the InstancePrincipalConfigurationProvider is tricky outside of an OCI context, because it tries
// to request a token from the internal OCI systems; this test-case just confirms that the expected error is
// observed, confirming that the instance-principal provider was instantiated.
config: OCIConfig{
Auth: OCIAuthConfig{
UseInstancePrincipal: true,
},
},
err: errors.New("error creating OCI instance principal config provider: failed to create a new key provider for instance principal"),
},
"invalid": {
config: OCIConfig{
Auth: OCIAuthConfig{
@ -192,7 +204,8 @@ hKRtDhmSdWBo3tJK12RrAe4t7CUe8gMgTvU7ExlcA3xQkseFPx9K
if err == nil {
require.NoError(t, err)
} else {
require.Equal(t, tc.err.Error(), err.Error())
// have to use prefix testing because the expected instance-principal error strings vary after a known prefix
require.Truef(t, strings.HasPrefix(err.Error(), tc.err.Error()), "observed: %s", err.Error())
}
})
}