mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
Merge pull request #1701 from ericrrath/oci-auth-instance-principal
OCI provider: add support for instance principal authentication
This commit is contained in:
commit
e7749e94d7
@ -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
15
main.go
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user