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

Added support for DDNS / RFC2136 #1889

Merged
merged 10 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
17 changes: 16 additions & 1 deletion backend/lib/services/cloudProviderSecrets.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,22 @@ const { Resources } = require('@gardener-dashboard/kube-client')
const createError = require('http-errors')
const { format: fmt } = require('util')
const { decodeBase64, encodeBase64 } = require('../utils')
const cleartextPropertyKeys = ['accessKeyID', 'subscriptionID', 'project', 'domainName', 'tenantName', 'authUrl', 'vsphereUsername', 'nsxtUsername', 'username', 'metalAPIURL', 'AWS_REGION']
const cleartextPropertyKeys = [
'accessKeyID',
'subscriptionID',
'project',
'domainName',
'tenantName',
'authUrl',
'vsphereUsername',
'nsxtUsername',
'username',
'metalAPIURL',
'AWS_REGION',
'Server',
'TSIGKeyName',
'Zone'
grolu marked this conversation as resolved.
Show resolved Hide resolved
]
const normalizedCleartextPropertyKeys = cleartextPropertyKeys.map(key => key.toLowerCase())
const cloudprofiles = require('./cloudprofiles')
const shoots = require('./shoots')
Expand Down
Binary file added frontend/src/assets/ddns.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions frontend/src/components/GVendorIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ const iconSrc = computed(() => {
return new URL('/src/assets/infoblox-dns.svg', import.meta.url)
case 'netlify-dns':
return new URL('/src/assets/netlify-dns.svg', import.meta.url)
case 'ddns':
return new URL('/src/assets/ddns.png', import.meta.url)

// os
case 'coreos':
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/components/Secrets/GSecretDetailsItemContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,21 @@ export default {
value: 'hidden',
},
]
case 'ddns':
return [
{
label: 'Server',
value: secretData.Server,
},
{
label: 'TSIG Key Name',
value: secretData.TSIGKeyName,
},
{
label: 'Zone',
value: secretData.Zone,
},
]
default:
return [
{
Expand Down
245 changes: 245 additions & 0 deletions frontend/src/components/Secrets/GSecretDialogDDNS.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
<!--
SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors
grolu marked this conversation as resolved.
Show resolved Hide resolved

SPDX-License-Identifier: Apache-2.0
-->

<template>
<g-secret-dialog
v-model="visible"
:data="secretData"
:secret-validations="v$"
holgerkoser marked this conversation as resolved.
Show resolved Hide resolved
:secret="secret"
vendor="netlify-dns"
grolu marked this conversation as resolved.
Show resolved Hide resolved
create-title="Add new DDNS (RFC2136) Secret"
replace-title="Replace DDNS (RFC2136) Secret"
grolu marked this conversation as resolved.
Show resolved Hide resolved
>
<template #secret-slot>
<div>
<v-text-field
v-model="server"
color="primary"
label="<host>:<port> of the authorive DNS server"
:error-messages="getErrorMessages(v$.server)"
type="text"
variant="underlined"
@update:model-value="v$.server.$touch()"
grolu marked this conversation as resolved.
Show resolved Hide resolved
@blur="v$.server.$touch()"
/>
</div>
<div>
<v-text-field
v-model="tsigKeyName"
color="primary"
label="TSIG Key Name"
:error-messages="getErrorMessages(v$.tsigKeyName)"
type="text"
variant="underlined"
@update:model-value="v$.tsigKeyName.$touch()"
grolu marked this conversation as resolved.
Show resolved Hide resolved
@blur="v$.tsigKeyName.$touch()"
/>
</div>
<div>
<v-text-field
v-model="tsigSecret"
color="primary"
label="TSIG Secret"
:error-messages="getErrorMessages(v$.tsigSecret)"
:append-icon="hideTSIGSecret ? 'mdi-eye' : 'mdi-eye-off'"
:type="hideTSIGSecret ? 'password' : 'text'"
variant="underlined"
@click:append="() => (hideTSIGSecret = !hideTSIGSecret)"
@update:model-value="v$.tsigSecret.$touch()"
grolu marked this conversation as resolved.
Show resolved Hide resolved
@blur="v$.tsigSecret.$touch()"
/>
</div>
<div>
<v-text-field
v-model="zone"
color="primary"
label="Zone"
:error-messages="getErrorMessages(v$.zone)"
type="text"
variant="underlined"
@update:model-value="v$.zone.$touch()"
grolu marked this conversation as resolved.
Show resolved Hide resolved
@blur="v$.zone.$touch()"
/>
</div>
<div>
<v-select
v-model="tsigSecretAlgorithm"
color="primary"
label="TSIG Secret Algorithm"
:items="secretAlgorithmItems"
variant="underlined"
/>
</div>
</template>

<template #help-slot>
grolu marked this conversation as resolved.
Show resolved Hide resolved
<div>
<p>
This DNS provider allows you to create and manage DNS entries for authoritive DNS server supporting dynamic updates with DNS messages following
<g-external-link url="https://datatracker.ietf.org/doc/html/rfc2136">
RFC2136
</g-external-link>
(DNS Update) like knot-dns or others.
</p>
<p>
The configuration is depending on the DNS server product. You need permissions for <code>update</code> and <code>transfer</code> (AXFR) actions on your zones and a TSIG secret.
</p>
<p>
For details see <g-external-link url="https://github.com/gardener/external-dns-management/tree/master/docs/rfc2136">
Gardener RFC2136 DNS Provider Documentation
</g-external-link>
grolu marked this conversation as resolved.
Show resolved Hide resolved
</p>
</div>
</template>
</g-secret-dialog>
</template>

<script>
holgerkoser marked this conversation as resolved.
Show resolved Hide resolved
import { useVuelidate } from '@vuelidate/core'
import { required } from '@vuelidate/validators'

import GSecretDialog from '@/components/Secrets/GSecretDialog'
import GExternalLink from '@/components/GExternalLink'

import { getErrorMessages } from '@/utils'
import {
withFieldName,
withMessage,
} from '@/utils/validators'

export default {
components: {
GSecretDialog,
GExternalLink,
},
props: {
modelValue: {
type: Boolean,
required: true,
},
secret: {
type: Object,
},
},
emits: [
'update:modelValue',
],
setup () {
return {
v$: useVuelidate(),
}
},
data () {
return {
server: undefined,
tsigKeyName: undefined,
tsigSecret: undefined,
zone: undefined,
tsigSecretAlgorithm: 'hmac-sha256',
hideTSIGSecret: true,
grolu marked this conversation as resolved.
Show resolved Hide resolved
secretAlgorithmItems: [
{
title: 'HMAC-SHA256 (default)',
value: 'hmac-sha256',
},
{
title: 'HMAC-SHA224',
value: 'hmac-sha224',
},
{
title: 'HMAC-SHA384',
value: 'hmac-sha384',
},
{
title: 'HMAC-SHA512',
value: 'hmac-sha512',
},
{
title: 'HMAC-SHA1',
value: 'hmac-sha1',
},
{
title: 'HMAC-MD5',
value: 'hmac-md5',
},
],
}
},
validations () {
return {
server: withFieldName('Server', {
required,
format: withMessage('Must have format <host>:<port>', value => {
return /^([^:]+):(\d+)$/.test(value)
grolu marked this conversation as resolved.
Show resolved Hide resolved
}),
}),
tsigKeyName: withFieldName('TSIG Key Name', {
required,
format: withMessage('Must end with a dot', value => {
return /\.$/.test(value)
}),
}),
tsigSecret: withFieldName('TSIG Secret', {
required,
}),
zone: withFieldName('Zone', {
required,
format: withMessage('Must be fully qualified', value => {
return /^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.$/.test(value)
}),
}),
}
},
computed: {
visible: {
get () {
return this.modelValue
},
set (modelValue) {
this.$emit('update:modelValue', modelValue)
},
},
valid () {
return !this.v$.$invalid
},
secretData () {
const data = {
Server: this.server,
TSIGKeyName: this.tsigKeyName,
TSIGSecret: this.tsigSecret,
Zone: this.zone,
}
if (this.tsigSecretAlgorithm !== 'hmac-sha256') {
data.TSIGSecretAlgorithm = this.tsigSecretAlgorithm
}
return data
},
isCreateMode () {
return !this.secret
},
},
watch: {
value: function (value) {
grolu marked this conversation as resolved.
Show resolved Hide resolved
if (value) {
this.reset()
}
},
},
methods: {
reset () {
this.v$.$reset()

this.server = undefined
this.tsigKeyName = undefined
this.tsigSecret = undefined
this.zone = undefined
this.tsigSecretAlgorithm = 'hmac-sha256'
},
getErrorMessages,
},
}
</script>
2 changes: 2 additions & 0 deletions frontend/src/components/Secrets/GSecretDialogWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import VsphereDialog from '@/components/Secrets/GSecretDialogVSphere'
import CloudflareDialog from '@/components/Secrets/GSecretDialogCloudflare'
import InfobloxDialog from '@/components/Secrets/GSecretDialogInfoblox'
import NetlifyDialog from '@/components/Secrets/GSecretDialogNetlify'
import DdnsDialog from '@/components/Secrets/GSecretDialogDDNS'
import DeleteDialog from '@/components/Secrets/GSecretDialogDelete'
import HcloudDialog from '@/components/Secrets/GSecretDialogHCloud'
import GenericDialog from '@/components/Secrets/GSecretDialogGeneric'
Expand All @@ -45,6 +46,7 @@ const components = {
CloudflareDialog,
InfobloxDialog,
NetlifyDialog,
DdnsDialog,
HcloudDialog,
GenericDialog,
DeleteDialog,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/store/gardenerExtension.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const useGardenerExtensionStore = defineStore('gardenerExtension', () =>
}

const sortedDnsProviderList = computed(() => {
const supportedProviderTypes = ['aws-route53', 'azure-dns', 'azure-private-dns', 'google-clouddns', 'openstack-designate', 'alicloud-dns', 'infoblox-dns', 'netlify-dns']
const supportedProviderTypes = ['aws-route53', 'azure-dns', 'azure-private-dns', 'google-clouddns', 'openstack-designate', 'alicloud-dns', 'infoblox-dns', 'netlify-dns', 'ddns']
const resources = flatMap(list.value, 'resources')
const dnsProvidersFromDnsRecords = filter(resources, ['kind', 'DNSRecord'])

Expand Down