Skip to content

Commit c6d42f2

Browse files
authored
Merge pull request #43 from datum-cloud/feature/domain-ownership-verification
feat: implement domain ownership verification with DNS and HTTP methods
2 parents 8bcecd2 + ca56c8d commit c6d42f2

20 files changed

Lines changed: 1544 additions & 69 deletions

api/v1alpha/domain_types.go

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,34 @@ import (
88

99
// +kubebuilder:object:root=true
1010
// +kubebuilder:subresource:status
11-
// +kubebuilder:printcolumn:name="Domain",type="string",JSONPath=".spec.domainName"
12-
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
13-
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].status`
14-
// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
15-
// +kubebuilder:printcolumn:name="Registrar",type="string",JSONPath=".status.registrar.ianaName"
16-
// +kubebuilder:printcolumn:name="DNSSEC",type="boolean",JSONPath=".status.registrar.dnssec.signed"
17-
// +kubebuilder:printcolumn:name="Expires",type="date",JSONPath=".status.registrar.expirationDate"
18-
// +kubebuilder:printcolumn:name="DNS-Verify",type="string",JSONPath=".status.verification.requiredDNSRecords[0].content"
1911

2012
// Domain represents a domain name in the Datum system
13+
//
14+
// +kubebuilder:printcolumn:name="Domain Name",type="string",JSONPath=".spec.domainName"
15+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
16+
// +kubebuilder:printcolumn:name="Verified",type="string",JSONPath=`.status.conditions[?(@.type=="Verified")].status`
17+
// +kubebuilder:printcolumn:name="Verification Message",type="string",JSONPath=`.status.conditions[?(@.type=="Verified")].message`,priority=1
2118
type Domain struct {
2219
metav1.TypeMeta `json:",inline"`
2320
metav1.ObjectMeta `json:"metadata,omitempty"`
2421

25-
Spec DomainSpec `json:"spec,omitempty"`
22+
// +kubebuilder:validation:Required
23+
Spec DomainSpec `json:"spec,omitempty"`
24+
25+
// +kubebuilder:default={conditions: {{type: "Verified", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "VerifiedDNS", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "VerifiedHTTP", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}}}
2626
Status DomainStatus `json:"status,omitempty"`
2727
}
2828

2929
// DomainSpec defines the desired state of Domain
3030
type DomainSpec struct {
3131
// DomainName is the fully qualified domain name (FQDN) to be managed
32+
//
3233
// +kubebuilder:validation:Required
3334
// +kubebuilder:validation:MinLength=1
3435
// +kubebuilder:validation:MaxLength=253
3536
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
37+
// +kubebuilder:validation:XValidation:message="A domain name is immutable and cannot be changed after creation",rule="oldSelf == '' || self == oldSelf"
38+
// +kubebuilder:validation:XValidation:message="Must have at least two segments separated by dots",rule="self.indexOf('.') != -1"
3639
DomainName string `json:"domainName"`
3740
}
3841

@@ -43,9 +46,49 @@ type DomainStatus struct {
4346
Conditions []metav1.Condition `json:"conditions,omitempty"`
4447
}
4548

49+
const (
50+
// This condition is true when Domain ownership has been verified via either
51+
// DNS or HTTP.
52+
DomainConditionVerified = "Verified"
53+
54+
// This condition tracks verification attempts via DNS.
55+
DomainConditionVerifiedDNS = "VerifiedDNS"
56+
57+
// This condition tracks verification attempts via HTTP.
58+
DomainConditionVerifiedHTTP = "VerifiedHTTP"
59+
)
60+
61+
const (
62+
// DomainReasonPendingVerification indicates domain verification is in
63+
// progress
64+
DomainReasonPendingVerification = "PendingVerification"
65+
66+
// DomainReasonVerificationRecordContentMismatch indicates the
67+
// verification record content does not match expected values
68+
DomainReasonVerificationRecordContentMismatch = "VerificationRecordContentMismatch"
69+
70+
// DomainReasonVerificationRecordNotFound indicates the verification record
71+
// was not found
72+
DomainReasonVerificationRecordNotFound = "RecordNotFound"
73+
74+
// DomainReasonVerificationUnexpectedResponse indicates that an unexpected
75+
// response was encountered during verification.
76+
DomainReasonVerificationUnexpectedResponse = "UnexpectedResponse"
77+
78+
// DomainReasonVerificationInternalError indicates that an internal error
79+
// was encountered during verification.
80+
DomainReasonVerificationInternalError = "InternalError"
81+
82+
// DomainReasonVerified indicates domain ownership has been successfully
83+
// verified
84+
DomainReasonVerified = "Verified"
85+
)
86+
4687
// DomainVerificationStatus represents the verification status of a domain
4788
type DomainVerificationStatus struct {
48-
RequiredDNSRecords []DNSVerificationExpectedRecord `json:"requiredDNSRecords,omitempty"`
89+
DNSRecord DNSVerificationRecord `json:"dnsRecord,omitempty"`
90+
HTTPToken HTTPVerificationToken `json:"httpToken,omitempty"`
91+
NextVerificationAttempt metav1.Time `json:"nextVerificationAttempt,omitempty"`
4992
}
5093

5194
// DomainRegistrarStatus represents the registrar information for a domain
@@ -66,13 +109,18 @@ type DNSSECStatus struct {
66109
Signed bool `json:"signed"`
67110
}
68111

69-
// DNSVerificationExpectedRecord represents a DNS record required for verification
70-
type DNSVerificationExpectedRecord struct {
112+
// DNSVerificationRecord represents a DNS record required for verification
113+
type DNSVerificationRecord struct {
71114
Name string `json:"name"`
72115
Type string `json:"type"`
73116
Content string `json:"content"`
74117
}
75118

119+
type HTTPVerificationToken struct {
120+
URL string `json:"url"`
121+
Body string `json:"body"`
122+
}
123+
76124
// +kubebuilder:object:root=true
77125

78126
// DomainList contains a list of Domain

api/v1alpha/zz_generated.deepcopy.go

Lines changed: 22 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ func main() {
227227
os.Exit(1)
228228
}
229229

230-
if err := (&controller.DomainReconciler{}).SetupWithManager(mgr); err != nil {
230+
if err := (&controller.DomainReconciler{
231+
Config: serverConfig,
232+
}).SetupWithManager(mgr); err != nil {
231233
setupLog.Error(err, "unable to create controller", "controller", "Domain")
232234
os.Exit(1)
233235
}

config/crd/bases/networking.datumapis.com_domains.yaml

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,17 @@ spec:
1616
versions:
1717
- additionalPrinterColumns:
1818
- jsonPath: .spec.domainName
19-
name: Domain
19+
name: Domain Name
2020
type: string
2121
- jsonPath: .metadata.creationTimestamp
2222
name: Age
2323
type: date
24-
- jsonPath: .status.conditions[?(@.type=="Ready")].status
25-
name: Ready
24+
- jsonPath: .status.conditions[?(@.type=="Verified")].status
25+
name: Verified
2626
type: string
27-
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
28-
name: Reason
29-
type: string
30-
- jsonPath: .status.registrar.ianaName
31-
name: Registrar
32-
type: string
33-
- jsonPath: .status.registrar.dnssec.signed
34-
name: DNSSEC
35-
type: boolean
36-
- jsonPath: .status.registrar.expirationDate
37-
name: Expires
38-
type: date
39-
- jsonPath: .status.verification.requiredDNSRecords[0].content
40-
name: DNS-Verify
27+
- jsonPath: .status.conditions[?(@.type=="Verified")].message
28+
name: Verification Message
29+
priority: 1
4130
type: string
4231
name: v1alpha
4332
schema:
@@ -71,10 +60,33 @@ spec:
7160
minLength: 1
7261
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
7362
type: string
63+
x-kubernetes-validations:
64+
- message: A domain name is immutable and cannot be changed after
65+
creation
66+
rule: oldSelf == '' || self == oldSelf
67+
- message: Must have at least two segments separated by dots
68+
rule: self.indexOf('.') != -1
7469
required:
7570
- domainName
7671
type: object
7772
status:
73+
default:
74+
conditions:
75+
- lastTransitionTime: "1970-01-01T00:00:00Z"
76+
message: Waiting for controller
77+
reason: Pending
78+
status: Unknown
79+
type: Verified
80+
- lastTransitionTime: "1970-01-01T00:00:00Z"
81+
message: Waiting for controller
82+
reason: Pending
83+
status: Unknown
84+
type: VerifiedDNS
85+
- lastTransitionTime: "1970-01-01T00:00:00Z"
86+
message: Waiting for controller
87+
reason: Pending
88+
status: Unknown
89+
type: VerifiedHTTP
7890
description: DomainStatus defines the observed state of Domain
7991
properties:
8092
conditions:
@@ -172,25 +184,38 @@ spec:
172184
description: DomainVerificationStatus represents the verification
173185
status of a domain
174186
properties:
175-
requiredDNSRecords:
176-
items:
177-
description: DNSVerificationExpectedRecord represents a DNS
178-
record required for verification
179-
properties:
180-
content:
181-
type: string
182-
name:
183-
type: string
184-
type:
185-
type: string
186-
required:
187-
- content
188-
- name
189-
- type
190-
type: object
191-
type: array
187+
dnsRecord:
188+
description: DNSVerificationRecord represents a DNS record required
189+
for verification
190+
properties:
191+
content:
192+
type: string
193+
name:
194+
type: string
195+
type:
196+
type: string
197+
required:
198+
- content
199+
- name
200+
- type
201+
type: object
202+
httpToken:
203+
properties:
204+
body:
205+
type: string
206+
url:
207+
type: string
208+
required:
209+
- body
210+
- url
211+
type: object
212+
nextVerificationAttempt:
213+
format: date-time
214+
type: string
192215
type: object
193216
type: object
217+
required:
218+
- spec
194219
type: object
195220
served: true
196221
storage: true
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
apiVersion: iam.miloapis.com/v1alpha1
3+
kind: ProtectedResource
4+
metadata:
5+
name: networking.datumapis.com-domain
6+
spec:
7+
serviceRef:
8+
name: "networking.datumapis.com"
9+
kind: Domain
10+
plural: domains
11+
singular: domain
12+
permissions:
13+
- list
14+
- get
15+
- watch
16+
- create
17+
- update
18+
- patch
19+
- delete
20+
parentResources:
21+
- apiGroup: resourcemanager.miloapis.com
22+
kind: Project

config/iam/protected-resources/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ resources:
1818
- networks.yaml
1919
- subnetclaims.yaml
2020
- subnets.yaml
21+
- domains.yaml

config/iam/roles/domain-admin.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: iam.miloapis.com/v1alpha1
2+
kind: Role
3+
metadata:
4+
name: networking.datumapis.com-domain-admin
5+
annotations:
6+
kubernetes.io/display-name: Domain Admin
7+
kubernetes.io/description: "Full access to domain resources"
8+
spec:
9+
launchStage: Beta
10+
inheritedRoles:
11+
- name: networking.datumapis.com-domain-viewer
12+
includedPermissions:
13+
- networking.datumapis.com/domains.create
14+
- networking.datumapis.com/domains.update
15+
- networking.datumapis.com/domains.patch
16+
- networking.datumapis.com/domains.delete
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: iam.miloapis.com/v1alpha1
2+
kind: Role
3+
metadata:
4+
name: networking.datumapis.com-domain-viewer
5+
annotations:
6+
kubernetes.io/display-name: Domain Viewer
7+
kubernetes.io/description: "View access to domain resources"
8+
spec:
9+
launchStage: Beta
10+
includedPermissions:
11+
- networking.datumapis.com/domains.list
12+
- networking.datumapis.com/domains.get
13+
- networking.datumapis.com/domains.watch

config/iam/roles/kustomization.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ resources:
99
- location-admin.yaml
1010
- networking-admin.yaml
1111
- networking-viewer.yaml
12+
- domain-admin.yaml
13+
- domain-viewer.yaml

config/iam/roles/networking-admin.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ spec:
1111
- name: networking.datumapis.com-viewer
1212
- name: networking.datumapis.com-gateway-admin
1313
- name: networking.datumapis.com-location-admin
14+
- name: networking.datumapis.com-domain-admin
1415
includedPermissions:
1516
- networking.datumapis.com/networks.create
1617
- networking.datumapis.com/networks.update

0 commit comments

Comments
 (0)