mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-07 01:56:57 +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
|
## 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].
|
For more information about OCI DNS see the documentation [here][1].
|
||||||
|
|
||||||
## Deploy ExternalDNS
|
## Deploy ExternalDNS
|
||||||
|
|
||||||
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
|
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.
|
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
|
```yaml
|
||||||
auth:
|
auth:
|
||||||
@ -37,7 +46,29 @@ Create a secret using the config file above:
|
|||||||
$ kubectl create secret generic external-dns-config --from-file=oci.yaml
|
$ 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.
|
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
|
[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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -312,7 +313,19 @@ func main() {
|
|||||||
)
|
)
|
||||||
case "oci":
|
case "oci":
|
||||||
var config *oci.OCIConfig
|
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 {
|
if err == nil {
|
||||||
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
|
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,8 @@ type Config struct {
|
|||||||
DynPassword string `secure:"yes"`
|
DynPassword string `secure:"yes"`
|
||||||
DynMinTTLSeconds int
|
DynMinTTLSeconds int
|
||||||
OCIConfigFile string
|
OCIConfigFile string
|
||||||
|
OCICompartmentOCID string
|
||||||
|
OCIAuthInstancePrincipal bool
|
||||||
InMemoryZones []string
|
InMemoryZones []string
|
||||||
OVHEndpoint string
|
OVHEndpoint string
|
||||||
OVHApiRateLimit int
|
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-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("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-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("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("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)
|
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/oracle/oci-go-sdk/common"
|
"github.com/oracle/oci-go-sdk/common"
|
||||||
|
"github.com/oracle/oci-go-sdk/common/auth"
|
||||||
"github.com/oracle/oci-go-sdk/dns"
|
"github.com/oracle/oci-go-sdk/dns"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -36,12 +37,13 @@ const ociRecordTTL = 300
|
|||||||
|
|
||||||
// OCIAuthConfig holds connection parameters for the OCI API.
|
// OCIAuthConfig holds connection parameters for the OCI API.
|
||||||
type OCIAuthConfig struct {
|
type OCIAuthConfig struct {
|
||||||
Region string `yaml:"region"`
|
Region string `yaml:"region"`
|
||||||
TenancyID string `yaml:"tenancy"`
|
TenancyID string `yaml:"tenancy"`
|
||||||
UserID string `yaml:"user"`
|
UserID string `yaml:"user"`
|
||||||
PrivateKey string `yaml:"key"`
|
PrivateKey string `yaml:"key"`
|
||||||
Fingerprint string `yaml:"fingerprint"`
|
Fingerprint string `yaml:"fingerprint"`
|
||||||
Passphrase string `yaml:"passphrase"`
|
Passphrase string `yaml:"passphrase"`
|
||||||
|
UseInstancePrincipal bool `yaml:"useInstancePrincipal"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OCIConfig holds the configuration for the OCI Provider.
|
// 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.
|
// NewOCIProvider initializes a new OCI DNS based Provider.
|
||||||
func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
|
func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
|
||||||
var client ociDNSClient
|
var client ociDNSClient
|
||||||
client, err := dns.NewDnsClientWithConfigurationProvider(common.NewRawConfigurationProvider(
|
var err error
|
||||||
cfg.Auth.TenancyID,
|
var configProvider common.ConfigurationProvider
|
||||||
cfg.Auth.UserID,
|
if cfg.Auth.UseInstancePrincipal {
|
||||||
cfg.Auth.Region,
|
configProvider, err = auth.InstancePrincipalConfigurationProvider()
|
||||||
cfg.Auth.Fingerprint,
|
if err != nil {
|
||||||
cfg.Auth.PrivateKey,
|
return nil, errors.Wrap(err, "error creating OCI instance principal config provider")
|
||||||
&cfg.Auth.Passphrase,
|
}
|
||||||
))
|
} 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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "initializing OCI DNS API client")
|
return nil, errors.Wrap(err, "initializing OCI DNS API client")
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package oci
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/oracle/oci-go-sdk/common"
|
"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": {
|
"invalid": {
|
||||||
config: OCIConfig{
|
config: OCIConfig{
|
||||||
Auth: OCIAuthConfig{
|
Auth: OCIAuthConfig{
|
||||||
@ -192,7 +204,8 @@ hKRtDhmSdWBo3tJK12RrAe4t7CUe8gMgTvU7ExlcA3xQkseFPx9K
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
} 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