2019-03-11 16:56:48 +00:00
package gcloud
2016-03-18 15:22:33 +00:00
import (
2019-01-02 19:45:17 +00:00
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"sort"
2016-03-18 15:22:33 +00:00
"testing"
"time"
2020-09-02 01:20:01 +00:00
"github.com/go-acme/lego/v4/platform/tester"
2018-09-24 19:07:20 +00:00
"github.com/stretchr/testify/require"
2016-03-18 15:22:33 +00:00
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/api/dns/v1"
)
2020-03-11 22:51:10 +00:00
const (
envDomain = envNamespace + "DOMAIN"
envServiceAccountFile = envNamespace + "SERVICE_ACCOUNT_FILE"
envMetadataHost = envNamespace + "METADATA_HOST"
envGoogleApplicationCredentials = "GOOGLE_APPLICATION_CREDENTIALS"
)
2018-10-16 15:52:57 +00:00
var envTest = tester . NewEnvTest (
2020-03-11 22:51:10 +00:00
EnvProject ,
envServiceAccountFile ,
envGoogleApplicationCredentials ,
envMetadataHost ,
EnvServiceAccount ) .
WithDomain ( envDomain ) .
2018-10-16 15:52:57 +00:00
WithLiveTestExtra ( func ( ) bool {
_ , err := google . DefaultClient ( context . Background ( ) , dns . NdevClouddnsReadwriteScope )
return err == nil
} )
2016-03-18 15:22:33 +00:00
2018-10-12 17:29:18 +00:00
func TestNewDNSProvider ( t * testing . T ) {
testCases := [ ] struct {
desc string
envVars map [ string ] string
expected string
} {
{
desc : "invalid credentials" ,
envVars : map [ string ] string {
2020-03-11 22:51:10 +00:00
EnvProject : "123" ,
envServiceAccountFile : "" ,
2018-10-12 17:29:18 +00:00
// as Travis run on GCE, we have to alter env
2020-03-11 22:51:10 +00:00
envGoogleApplicationCredentials : "not-a-secret-file" ,
envMetadataHost : "http://lego.wtf" , // defined here to avoid the client cache.
2018-10-12 17:29:18 +00:00
} ,
2020-12-09 19:52:21 +00:00
// the error message varies according to the OS used.
expected : "googlecloud: unable to get Google Cloud client: google: error getting credentials using GOOGLE_APPLICATION_CREDENTIALS environment variable: " ,
2018-10-12 17:29:18 +00:00
} ,
{
desc : "missing project" ,
envVars : map [ string ] string {
2020-03-11 22:51:10 +00:00
EnvProject : "" ,
envServiceAccountFile : "" ,
2020-01-04 16:18:34 +00:00
// as Travis run on GCE, we have to alter env
2020-03-11 22:51:10 +00:00
envMetadataHost : "http://lego.wtf" ,
2018-10-12 17:29:18 +00:00
} ,
expected : "googlecloud: project name missing" ,
} ,
{
2019-02-01 12:14:57 +00:00
desc : "success key file" ,
2018-10-12 17:29:18 +00:00
envVars : map [ string ] string {
2020-03-11 22:51:10 +00:00
EnvProject : "" ,
envServiceAccountFile : "fixtures/gce_account_service_file.json" ,
2018-10-12 17:29:18 +00:00
} ,
} ,
2019-02-01 12:14:57 +00:00
{
desc : "success key" ,
envVars : map [ string ] string {
2020-03-11 22:51:10 +00:00
EnvProject : "" ,
EnvServiceAccount : ` { "project_id": "A","type": "service_account","client_email": "foo@bar.com","private_key_id": "pki","private_key": "pk","token_uri": "/token","client_secret": "secret","client_id": "C","refresh_token": "D"} ` ,
2019-02-01 12:14:57 +00:00
} ,
} ,
2016-03-18 15:22:33 +00:00
}
2018-06-11 15:32:50 +00:00
2018-10-12 17:29:18 +00:00
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
2018-10-16 15:52:57 +00:00
defer envTest . RestoreEnv ( )
envTest . ClearEnv ( )
envTest . Apply ( test . envVars )
2018-10-12 17:29:18 +00:00
p , err := NewDNSProvider ( )
2021-03-04 19:16:59 +00:00
if test . expected == "" {
2018-10-12 17:29:18 +00:00
require . NoError ( t , err )
require . NotNil ( t , p )
require . NotNil ( t , p . config )
require . NotNil ( t , p . client )
} else {
2020-12-09 19:52:21 +00:00
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , test . expected )
2018-10-12 17:29:18 +00:00
}
} )
2016-03-18 15:22:33 +00:00
}
}
2018-10-12 17:29:18 +00:00
func TestNewDNSProviderConfig ( t * testing . T ) {
testCases := [ ] struct {
desc string
project string
expected string
} {
{
desc : "invalid project" ,
project : "123" ,
expected : "googlecloud: unable to create Google Cloud DNS service: client is nil" ,
} ,
{
desc : "missing project" ,
expected : "googlecloud: unable to create Google Cloud DNS service: client is nil" ,
} ,
}
2018-06-11 15:32:50 +00:00
2018-10-12 17:29:18 +00:00
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
2018-10-16 15:52:57 +00:00
defer envTest . RestoreEnv ( )
envTest . ClearEnv ( )
2018-10-12 17:29:18 +00:00
config := NewDefaultConfig ( )
config . Project = test . project
p , err := NewDNSProviderConfig ( config )
2021-03-04 19:16:59 +00:00
if test . expected == "" {
2018-10-12 17:29:18 +00:00
require . NoError ( t , err )
require . NotNil ( t , p )
require . NotNil ( t , p . config )
require . NotNil ( t , p . client )
} else {
require . EqualError ( t , err , test . expected )
}
} )
}
2016-03-18 15:22:33 +00:00
}
2019-01-02 19:45:17 +00:00
func TestPresentNoExistingRR ( t * testing . T ) {
mux := http . NewServeMux ( )
// getHostedZone: /manhattan/managedZones?alt=json&dnsName=lego.wtf.
mux . HandleFunc ( "/manhattan/managedZones" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
mzlrs := & dns . ManagedZonesListResponse {
ManagedZones : [ ] * dns . ManagedZone {
2019-04-12 18:37:29 +00:00
{ Name : "test" , Visibility : "public" } ,
2019-01-02 19:45:17 +00:00
} ,
}
err := json . NewEncoder ( w ) . Encode ( mzlrs )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
// findTxtRecords: /manhattan/managedZones/test/rrsets?alt=json&name=_acme-challenge.lego.wtf.&type=TXT
mux . HandleFunc ( "/manhattan/managedZones/test/rrsets" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
rrslr := & dns . ResourceRecordSetsListResponse {
Rrsets : [ ] * dns . ResourceRecordSet { } ,
}
err := json . NewEncoder ( w ) . Encode ( rrslr )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
// applyChanges [Create]: /manhattan/managedZones/test/changes?alt=json
mux . HandleFunc ( "/manhattan/managedZones/test/changes" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodPost {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
var chgReq dns . Change
if err := json . NewDecoder ( r . Body ) . Decode ( & chgReq ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
chgResp := chgReq
chgResp . Status = changeStatusDone
if err := json . NewEncoder ( w ) . Encode ( chgResp ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
server := httptest . NewServer ( mux )
config := NewDefaultConfig ( )
config . HTTPClient = & http . Client { }
config . Project = "manhattan"
p , err := NewDNSProviderConfig ( config )
require . NoError ( t , err )
p . client . BasePath = server . URL
domain := "lego.wtf"
err = p . Present ( domain , "" , "" )
require . NoError ( t , err )
}
func TestPresentWithExistingRR ( t * testing . T ) {
mux := http . NewServeMux ( )
// getHostedZone: /manhattan/managedZones?alt=json&dnsName=lego.wtf.
mux . HandleFunc ( "/manhattan/managedZones" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
mzlrs := & dns . ManagedZonesListResponse {
ManagedZones : [ ] * dns . ManagedZone {
2019-04-12 18:37:29 +00:00
{ Name : "test" , Visibility : "public" } ,
2019-01-02 19:45:17 +00:00
} ,
}
err := json . NewEncoder ( w ) . Encode ( mzlrs )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
// findTxtRecords: /manhattan/managedZones/test/rrsets?alt=json&name=_acme-challenge.lego.wtf.&type=TXT
mux . HandleFunc ( "/manhattan/managedZones/test/rrsets" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
rrslr := & dns . ResourceRecordSetsListResponse {
Rrsets : [ ] * dns . ResourceRecordSet { {
Name : "_acme-challenge.lego.wtf." ,
Rrdatas : [ ] string { ` "X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" ` , ` "huji" ` } ,
Ttl : 120 ,
Type : "TXT" ,
} } ,
}
err := json . NewEncoder ( w ) . Encode ( rrslr )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
// applyChanges [Create]: /manhattan/managedZones/test/changes?alt=json
mux . HandleFunc ( "/manhattan/managedZones/test/changes" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodPost {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
var chgReq dns . Change
if err := json . NewDecoder ( r . Body ) . Decode ( & chgReq ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
if len ( chgReq . Additions ) > 0 {
sort . Strings ( chgReq . Additions [ 0 ] . Rrdatas )
}
var prevVal string
for _ , addition := range chgReq . Additions {
for _ , value := range addition . Rrdatas {
if prevVal == value {
http . Error ( w , fmt . Sprintf ( "The resource %s already exists" , value ) , http . StatusConflict )
return
}
prevVal = value
}
}
chgResp := chgReq
chgResp . Status = changeStatusDone
if err := json . NewEncoder ( w ) . Encode ( chgResp ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
server := httptest . NewServer ( mux )
config := NewDefaultConfig ( )
config . HTTPClient = & http . Client { }
config . Project = "manhattan"
p , err := NewDNSProviderConfig ( config )
require . NoError ( t , err )
p . client . BasePath = server . URL
domain := "lego.wtf"
err = p . Present ( domain , "" , "" )
require . NoError ( t , err )
}
func TestPresentSkipExistingRR ( t * testing . T ) {
mux := http . NewServeMux ( )
// getHostedZone: /manhattan/managedZones?alt=json&dnsName=lego.wtf.
mux . HandleFunc ( "/manhattan/managedZones" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
mzlrs := & dns . ManagedZonesListResponse {
ManagedZones : [ ] * dns . ManagedZone {
2019-04-12 18:37:29 +00:00
{ Name : "test" , Visibility : "public" } ,
2019-01-02 19:45:17 +00:00
} ,
}
err := json . NewEncoder ( w ) . Encode ( mzlrs )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
// findTxtRecords: /manhattan/managedZones/test/rrsets?alt=json&name=_acme-challenge.lego.wtf.&type=TXT
mux . HandleFunc ( "/manhattan/managedZones/test/rrsets" , func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
rrslr := & dns . ResourceRecordSetsListResponse {
Rrsets : [ ] * dns . ResourceRecordSet { {
Name : "_acme-challenge.lego.wtf." ,
Rrdatas : [ ] string { ` "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" ` , ` "X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" ` , ` "huji" ` } ,
Ttl : 120 ,
Type : "TXT" ,
} } ,
}
err := json . NewEncoder ( w ) . Encode ( rrslr )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
} )
server := httptest . NewServer ( mux )
config := NewDefaultConfig ( )
config . HTTPClient = & http . Client { }
config . Project = "manhattan"
p , err := NewDNSProviderConfig ( config )
require . NoError ( t , err )
p . client . BasePath = server . URL
domain := "lego.wtf"
err = p . Present ( domain , "" , "" )
require . NoError ( t , err )
}
2018-10-12 17:29:18 +00:00
func TestLivePresent ( t * testing . T ) {
2018-10-16 15:52:57 +00:00
if ! envTest . IsLiveTest ( ) {
2016-03-18 15:22:33 +00:00
t . Skip ( "skipping live test" )
}
2018-10-16 15:52:57 +00:00
envTest . RestoreEnv ( )
2020-03-11 22:51:10 +00:00
provider , err := NewDNSProviderCredentials ( envTest . GetValue ( EnvProject ) )
2018-09-24 19:07:20 +00:00
require . NoError ( t , err )
2016-03-18 15:22:33 +00:00
2018-10-16 15:52:57 +00:00
err = provider . Present ( envTest . GetDomain ( ) , "" , "123d==" )
2018-09-24 19:07:20 +00:00
require . NoError ( t , err )
2016-03-18 15:22:33 +00:00
}
2018-10-12 17:29:18 +00:00
func TestLivePresentMultiple ( t * testing . T ) {
2018-10-16 15:52:57 +00:00
if ! envTest . IsLiveTest ( ) {
2016-11-02 14:47:17 +00:00
t . Skip ( "skipping live test" )
}
2018-10-16 15:52:57 +00:00
envTest . RestoreEnv ( )
2020-03-11 22:51:10 +00:00
provider , err := NewDNSProviderCredentials ( envTest . GetValue ( EnvProject ) )
2018-09-24 19:07:20 +00:00
require . NoError ( t , err )
2016-11-02 14:47:17 +00:00
// Check that we're able to create multiple entries
2018-10-16 15:52:57 +00:00
err = provider . Present ( envTest . GetDomain ( ) , "1" , "123d==" )
2018-09-24 19:07:20 +00:00
require . NoError ( t , err )
2018-10-12 17:29:18 +00:00
2018-10-16 15:52:57 +00:00
err = provider . Present ( envTest . GetDomain ( ) , "2" , "123d==" )
2018-09-24 19:07:20 +00:00
require . NoError ( t , err )
2016-11-02 14:47:17 +00:00
}
2018-10-12 17:29:18 +00:00
func TestLiveCleanUp ( t * testing . T ) {
2018-10-16 15:52:57 +00:00
if ! envTest . IsLiveTest ( ) {
2016-03-18 15:22:33 +00:00
t . Skip ( "skipping live test" )
}
2018-10-16 15:52:57 +00:00
envTest . RestoreEnv ( )
2020-03-11 22:51:10 +00:00
provider , err := NewDNSProviderCredentials ( envTest . GetValue ( EnvProject ) )
2018-09-24 19:07:20 +00:00
require . NoError ( t , err )
2016-03-18 15:22:33 +00:00
2018-10-12 17:29:18 +00:00
time . Sleep ( 1 * time . Second )
2018-10-16 15:52:57 +00:00
err = provider . CleanUp ( envTest . GetDomain ( ) , "" , "123d==" )
2018-09-24 19:07:20 +00:00
require . NoError ( t , err )
2016-03-18 15:22:33 +00:00
}