Merge tag 'v1.7.4' into master
This commit is contained in:
commit
d3ae88f108
154 changed files with 4356 additions and 1285 deletions
408
vendor/github.com/Azure/go-autorest/autorest/azure/auth/auth.go
generated
vendored
Normal file
408
vendor/github.com/Azure/go-autorest/autorest/azure/auth/auth.go
generated
vendored
Normal file
|
@ -0,0 +1,408 @@
|
|||
package auth
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/dimchansky/utfbom"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
// NewAuthorizerFromEnvironment creates an Authorizer configured from environment variables in the order:
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
// 3. Username password
|
||||
// 4. MSI
|
||||
func NewAuthorizerFromEnvironment() (autorest.Authorizer, error) {
|
||||
tenantID := os.Getenv("AZURE_TENANT_ID")
|
||||
clientID := os.Getenv("AZURE_CLIENT_ID")
|
||||
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
||||
certificatePath := os.Getenv("AZURE_CERTIFICATE_PATH")
|
||||
certificatePassword := os.Getenv("AZURE_CERTIFICATE_PASSWORD")
|
||||
username := os.Getenv("AZURE_USERNAME")
|
||||
password := os.Getenv("AZURE_PASSWORD")
|
||||
envName := os.Getenv("AZURE_ENVIRONMENT")
|
||||
resource := os.Getenv("AZURE_AD_RESOURCE")
|
||||
|
||||
var env azure.Environment
|
||||
if envName == "" {
|
||||
env = azure.PublicCloud
|
||||
} else {
|
||||
var err error
|
||||
env, err = azure.EnvironmentFromName(envName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if resource == "" {
|
||||
resource = env.ResourceManagerEndpoint
|
||||
}
|
||||
|
||||
//1.Client Credentials
|
||||
if clientSecret != "" {
|
||||
config := NewClientCredentialsConfig(clientID, clientSecret, tenantID)
|
||||
config.AADEndpoint = env.ActiveDirectoryEndpoint
|
||||
config.Resource = resource
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
//2. Client Certificate
|
||||
if certificatePath != "" {
|
||||
config := NewClientCertificateConfig(certificatePath, certificatePassword, clientID, tenantID)
|
||||
config.AADEndpoint = env.ActiveDirectoryEndpoint
|
||||
config.Resource = resource
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
//3. Username Password
|
||||
if username != "" && password != "" {
|
||||
config := NewUsernamePasswordConfig(username, password, clientID, tenantID)
|
||||
config.AADEndpoint = env.ActiveDirectoryEndpoint
|
||||
config.Resource = resource
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
// 4. MSI
|
||||
config := NewMSIConfig()
|
||||
config.Resource = resource
|
||||
config.ClientID = clientID
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
// NewAuthorizerFromFile creates an Authorizer configured from a configuration file.
|
||||
func NewAuthorizerFromFile(baseURI string) (autorest.Authorizer, error) {
|
||||
fileLocation := os.Getenv("AZURE_AUTH_LOCATION")
|
||||
if fileLocation == "" {
|
||||
return nil, errors.New("auth file not found. Environment variable AZURE_AUTH_LOCATION is not set")
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fileLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Auth file might be encoded
|
||||
decoded, err := decode(contents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file := file{}
|
||||
err = json.Unmarshal(decoded, &file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource, err := getResourceForToken(file, baseURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := adal.NewOAuthConfig(file.ActiveDirectoryEndpoint, file.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*config, file.ClientID, file.ClientSecret, resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// File represents the authentication file
|
||||
type file struct {
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpointUrl,omitempty"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpointUrl,omitempty"`
|
||||
GraphResourceID string `json:"activeDirectoryGraphResourceId,omitempty"`
|
||||
SQLManagementEndpoint string `json:"sqlManagementEndpointUrl,omitempty"`
|
||||
GalleryEndpoint string `json:"galleryEndpointUrl,omitempty"`
|
||||
ManagementEndpoint string `json:"managementEndpointUrl,omitempty"`
|
||||
}
|
||||
|
||||
func decode(b []byte) ([]byte, error) {
|
||||
reader, enc := utfbom.Skip(bytes.NewReader(b))
|
||||
|
||||
switch enc {
|
||||
case utfbom.UTF16LittleEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.LittleEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
case utfbom.UTF16BigEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.BigEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
}
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
|
||||
func getResourceForToken(f file, baseURI string) (string, error) {
|
||||
// Compare dafault base URI from the SDK to the endpoints from the public cloud
|
||||
// Base URI and token resource are the same string. This func finds the authentication
|
||||
// file field that matches the SDK base URI. The SDK defines the public cloud
|
||||
// endpoint as its default base URI
|
||||
if !strings.HasSuffix(baseURI, "/") {
|
||||
baseURI += "/"
|
||||
}
|
||||
switch baseURI {
|
||||
case azure.PublicCloud.ServiceManagementEndpoint:
|
||||
return f.ManagementEndpoint, nil
|
||||
case azure.PublicCloud.ResourceManagerEndpoint:
|
||||
return f.ResourceManagerEndpoint, nil
|
||||
case azure.PublicCloud.ActiveDirectoryEndpoint:
|
||||
return f.ActiveDirectoryEndpoint, nil
|
||||
case azure.PublicCloud.GalleryEndpoint:
|
||||
return f.GalleryEndpoint, nil
|
||||
case azure.PublicCloud.GraphEndpoint:
|
||||
return f.GraphResourceID, nil
|
||||
}
|
||||
return "", fmt.Errorf("auth: base URI not found in endpoints")
|
||||
}
|
||||
|
||||
// NewClientCredentialsConfig creates an AuthorizerConfig object configured to obtain an Authorizer through Client Credentials.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewClientCredentialsConfig(clientID string, clientSecret string, tenantID string) ClientCredentialsConfig {
|
||||
return ClientCredentialsConfig{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientCertificateConfig creates a ClientCertificateConfig object configured to obtain an Authorizer through client certificate.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewClientCertificateConfig(certificatePath string, certificatePassword string, clientID string, tenantID string) ClientCertificateConfig {
|
||||
return ClientCertificateConfig{
|
||||
CertificatePath: certificatePath,
|
||||
CertificatePassword: certificatePassword,
|
||||
ClientID: clientID,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewUsernamePasswordConfig creates an UsernamePasswordConfig object configured to obtain an Authorizer through username and password.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewUsernamePasswordConfig(username string, password string, clientID string, tenantID string) UsernamePasswordConfig {
|
||||
return UsernamePasswordConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
ClientID: clientID,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMSIConfig creates an MSIConfig object configured to obtain an Authorizer through MSI.
|
||||
func NewMSIConfig() MSIConfig {
|
||||
return MSIConfig{
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeviceFlowConfig creates a DeviceFlowConfig object configured to obtain an Authorizer through device flow.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewDeviceFlowConfig(clientID string, tenantID string) DeviceFlowConfig {
|
||||
return DeviceFlowConfig{
|
||||
ClientID: clientID,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
//AuthorizerConfig provides an authorizer from the configuration provided.
|
||||
type AuthorizerConfig interface {
|
||||
Authorizer() (autorest.Authorizer, error)
|
||||
}
|
||||
|
||||
// ClientCredentialsConfig provides the options to get a bearer authorizer from client credentials.
|
||||
type ClientCredentialsConfig struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from client credentials.
|
||||
func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from client credentials: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// ClientCertificateConfig provides the options to get a bearer authorizer from a client certificate.
|
||||
type ClientCertificateConfig struct {
|
||||
ClientID string
|
||||
CertificatePath string
|
||||
CertificatePassword string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets an authorizer object from client certificate.
|
||||
func (ccc ClientCertificateConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
|
||||
|
||||
certData, err := ioutil.ReadFile(ccc.CertificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", ccc.CertificatePath, err)
|
||||
}
|
||||
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, ccc.CertificatePassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from certificate auth: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// DeviceFlowConfig provides the options to get a bearer authorizer using device flow authentication.
|
||||
type DeviceFlowConfig struct {
|
||||
ClientID string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from device flow.
|
||||
func (dfc DeviceFlowConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
oauthClient := &autorest.Client{}
|
||||
oauthConfig, err := adal.NewOAuthConfig(dfc.AADEndpoint, dfc.TenantID)
|
||||
deviceCode, err := adal.InitiateDeviceAuth(oauthClient, *oauthConfig, dfc.ClientID, dfc.AADEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
log.Println(*deviceCode.Message)
|
||||
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, dfc.ClientID, dfc.Resource, *token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
privateKey, certificate, err := pkcs12.Decode(pkcs, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
|
||||
if !isRsaKey {
|
||||
return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key")
|
||||
}
|
||||
|
||||
return certificate, rsaPrivateKey, nil
|
||||
}
|
||||
|
||||
// UsernamePasswordConfig provides the options to get a bearer authorizer from a username and a password.
|
||||
type UsernamePasswordConfig struct {
|
||||
ClientID string
|
||||
Username string
|
||||
Password string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from a username and a password.
|
||||
func (ups UsernamePasswordConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(ups.AADEndpoint, ups.TenantID)
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromUsernamePassword(*oauthConfig, ups.ClientID, ups.Username, ups.Password, ups.Resource)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from username and password auth: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// MSIConfig provides the options to get a bearer authorizer through MSI.
|
||||
type MSIConfig struct {
|
||||
Resource string
|
||||
ClientID string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from MSI.
|
||||
func (mc MSIConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
msiEndpoint, err := adal.GetMSIVMEndpoint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, mc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
6
vendor/github.com/containous/mux/regexp.go
generated
vendored
6
vendor/github.com/containous/mux/regexp.go
generated
vendored
|
@ -53,6 +53,12 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash,
|
|||
varsN := make([]string, len(idxs)/2)
|
||||
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
||||
pattern := bytes.NewBufferString("")
|
||||
|
||||
// Host matching is case insensitive
|
||||
if matchHost {
|
||||
fmt.Fprint(pattern, "(?i)")
|
||||
}
|
||||
|
||||
pattern.WriteByte('^')
|
||||
reverse := bytes.NewBufferString("")
|
||||
var end int
|
||||
|
|
201
vendor/github.com/dimchansky/utfbom/LICENSE
generated
vendored
Normal file
201
vendor/github.com/dimchansky/utfbom/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
174
vendor/github.com/dimchansky/utfbom/utfbom.go
generated
vendored
Normal file
174
vendor/github.com/dimchansky/utfbom/utfbom.go
generated
vendored
Normal file
|
@ -0,0 +1,174 @@
|
|||
// Package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary.
|
||||
// It wraps an io.Reader object, creating another object (Reader) that also implements the io.Reader
|
||||
// interface but provides automatic BOM checking and removing as necessary.
|
||||
package utfbom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Encoding is type alias for detected UTF encoding.
|
||||
type Encoding int
|
||||
|
||||
// Constants to identify detected UTF encodings.
|
||||
const (
|
||||
// Unknown encoding, returned when no BOM was detected
|
||||
Unknown Encoding = iota
|
||||
|
||||
// UTF8, BOM bytes: EF BB BF
|
||||
UTF8
|
||||
|
||||
// UTF-16, big-endian, BOM bytes: FE FF
|
||||
UTF16BigEndian
|
||||
|
||||
// UTF-16, little-endian, BOM bytes: FF FE
|
||||
UTF16LittleEndian
|
||||
|
||||
// UTF-32, big-endian, BOM bytes: 00 00 FE FF
|
||||
UTF32BigEndian
|
||||
|
||||
// UTF-32, little-endian, BOM bytes: FF FE 00 00
|
||||
UTF32LittleEndian
|
||||
)
|
||||
|
||||
const maxConsecutiveEmptyReads = 100
|
||||
|
||||
// Skip creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||
// It also returns the encoding detected by the BOM.
|
||||
// If the detected encoding is not needed, you can call the SkipOnly function.
|
||||
func Skip(rd io.Reader) (*Reader, Encoding) {
|
||||
// Is it already a Reader?
|
||||
b, ok := rd.(*Reader)
|
||||
if ok {
|
||||
return b, Unknown
|
||||
}
|
||||
|
||||
enc, left, err := detectUtf(rd)
|
||||
return &Reader{
|
||||
rd: rd,
|
||||
buf: left,
|
||||
err: err,
|
||||
}, enc
|
||||
}
|
||||
|
||||
// SkipOnly creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||
func SkipOnly(rd io.Reader) *Reader {
|
||||
r, _ := Skip(rd)
|
||||
return r
|
||||
}
|
||||
|
||||
// Reader implements automatic BOM (Unicode Byte Order Mark) checking and
|
||||
// removing as necessary for an io.Reader object.
|
||||
type Reader struct {
|
||||
rd io.Reader // reader provided by the client
|
||||
buf []byte // buffered data
|
||||
err error // last error
|
||||
}
|
||||
|
||||
// Read is an implementation of io.Reader interface.
|
||||
// The bytes are taken from the underlying Reader, but it checks for BOMs, removing them as necessary.
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if r.buf == nil {
|
||||
if r.err != nil {
|
||||
return 0, r.readErr()
|
||||
}
|
||||
|
||||
return r.rd.Read(p)
|
||||
}
|
||||
|
||||
// copy as much as we can
|
||||
n = copy(p, r.buf)
|
||||
r.buf = nilIfEmpty(r.buf[n:])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readErr() error {
|
||||
err := r.err
|
||||
r.err = nil
|
||||
return err
|
||||
}
|
||||
|
||||
var errNegativeRead = errors.New("utfbom: reader returned negative count from Read")
|
||||
|
||||
func detectUtf(rd io.Reader) (enc Encoding, buf []byte, err error) {
|
||||
buf, err = readBOM(rd)
|
||||
|
||||
if len(buf) >= 4 {
|
||||
if isUTF32BigEndianBOM4(buf) {
|
||||
return UTF32BigEndian, nilIfEmpty(buf[4:]), err
|
||||
}
|
||||
if isUTF32LittleEndianBOM4(buf) {
|
||||
return UTF32LittleEndian, nilIfEmpty(buf[4:]), err
|
||||
}
|
||||
}
|
||||
|
||||
if len(buf) > 2 && isUTF8BOM3(buf) {
|
||||
return UTF8, nilIfEmpty(buf[3:]), err
|
||||
}
|
||||
|
||||
if (err != nil && err != io.EOF) || (len(buf) < 2) {
|
||||
return Unknown, nilIfEmpty(buf), err
|
||||
}
|
||||
|
||||
if isUTF16BigEndianBOM2(buf) {
|
||||
return UTF16BigEndian, nilIfEmpty(buf[2:]), err
|
||||
}
|
||||
if isUTF16LittleEndianBOM2(buf) {
|
||||
return UTF16LittleEndian, nilIfEmpty(buf[2:]), err
|
||||
}
|
||||
|
||||
return Unknown, nilIfEmpty(buf), err
|
||||
}
|
||||
|
||||
func readBOM(rd io.Reader) (buf []byte, err error) {
|
||||
const maxBOMSize = 4
|
||||
var bom [maxBOMSize]byte // used to read BOM
|
||||
|
||||
// read as many bytes as possible
|
||||
for nEmpty, n := 0, 0; err == nil && len(buf) < maxBOMSize; buf = bom[:len(buf)+n] {
|
||||
if n, err = rd.Read(bom[len(buf):]); n < 0 {
|
||||
panic(errNegativeRead)
|
||||
}
|
||||
if n > 0 {
|
||||
nEmpty = 0
|
||||
} else {
|
||||
nEmpty++
|
||||
if nEmpty >= maxConsecutiveEmptyReads {
|
||||
err = io.ErrNoProgress
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isUTF32BigEndianBOM4(buf []byte) bool {
|
||||
return buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF
|
||||
}
|
||||
|
||||
func isUTF32LittleEndianBOM4(buf []byte) bool {
|
||||
return buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00
|
||||
}
|
||||
|
||||
func isUTF8BOM3(buf []byte) bool {
|
||||
return buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF
|
||||
}
|
||||
|
||||
func isUTF16BigEndianBOM2(buf []byte) bool {
|
||||
return buf[0] == 0xFE && buf[1] == 0xFF
|
||||
}
|
||||
|
||||
func isUTF16LittleEndianBOM2(buf []byte) bool {
|
||||
return buf[0] == 0xFF && buf[1] == 0xFE
|
||||
}
|
||||
|
||||
func nilIfEmpty(buf []byte) (res []byte) {
|
||||
if len(buf) > 0 {
|
||||
res = buf
|
||||
}
|
||||
return
|
||||
}
|
22
vendor/github.com/edeckers/auroradnsclient/client.go
generated
vendored
22
vendor/github.com/edeckers/auroradnsclient/client.go
generated
vendored
|
@ -1,22 +0,0 @@
|
|||
package auroradnsclient
|
||||
|
||||
import (
|
||||
"github.com/edeckers/auroradnsclient/requests"
|
||||
)
|
||||
|
||||
// AuroraDNSClient is a client for accessing the Aurora DNS API
|
||||
type AuroraDNSClient struct {
|
||||
requestor *requests.AuroraRequestor
|
||||
}
|
||||
|
||||
// NewAuroraDNSClient instantiates a new client
|
||||
func NewAuroraDNSClient(endpoint string, userID string, key string) (*AuroraDNSClient, error) {
|
||||
requestor, err := requests.NewAuroraRequestor(endpoint, userID, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AuroraDNSClient{
|
||||
requestor: requestor,
|
||||
}, nil
|
||||
}
|
11
vendor/github.com/edeckers/auroradnsclient/errors.go
generated
vendored
11
vendor/github.com/edeckers/auroradnsclient/errors.go
generated
vendored
|
@ -1,11 +0,0 @@
|
|||
package auroradnsclient
|
||||
|
||||
// AuroraDNSError describes the format of a generic AuroraDNS API error
|
||||
type AuroraDNSError struct {
|
||||
ErrorCode string `json:"error"`
|
||||
Message string `json:"errormsg"`
|
||||
}
|
||||
|
||||
func (e AuroraDNSError) Error() string {
|
||||
return e.Message
|
||||
}
|
75
vendor/github.com/edeckers/auroradnsclient/records.go
generated
vendored
75
vendor/github.com/edeckers/auroradnsclient/records.go
generated
vendored
|
@ -1,75 +0,0 @@
|
|||
package auroradnsclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/edeckers/auroradnsclient/records"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetRecords returns a list of all records in given zone
|
||||
func (client *AuroraDNSClient) GetRecords(zoneID string) ([]records.GetRecordsResponse, error) {
|
||||
logrus.Debugf("GetRecords(%s)", zoneID)
|
||||
relativeURL := fmt.Sprintf("zones/%s/records", zoneID)
|
||||
|
||||
response, err := client.requestor.Request(relativeURL, "GET", []byte(""))
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to receive records: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respData []records.GetRecordsResponse
|
||||
err = json.Unmarshal(response, &respData)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to unmarshall response: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
}
|
||||
|
||||
// CreateRecord creates a new record in given zone
|
||||
func (client *AuroraDNSClient) CreateRecord(zoneID string, data records.CreateRecordRequest) (*records.CreateRecordResponse, error) {
|
||||
logrus.Debugf("CreateRecord(%s, %+v)", zoneID, data)
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to marshall request body: %s", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
relativeURL := fmt.Sprintf("zones/%s/records", zoneID)
|
||||
|
||||
response, err := client.requestor.Request(relativeURL, "POST", body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to create record: %s", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respData *records.CreateRecordResponse
|
||||
err = json.Unmarshal(response, &respData)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to unmarshall response: %s", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
}
|
||||
|
||||
// RemoveRecord removes a record corresponding to a particular id in a given zone
|
||||
func (client *AuroraDNSClient) RemoveRecord(zoneID string, recordID string) (*records.RemoveRecordResponse, error) {
|
||||
logrus.Debugf("RemoveRecord(%s, %s)", zoneID, recordID)
|
||||
relativeURL := fmt.Sprintf("zones/%s/records/%s", zoneID, recordID)
|
||||
|
||||
_, err := client.requestor.Request(relativeURL, "DELETE", nil)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to remove record: %s", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &records.RemoveRecordResponse{}, nil
|
||||
}
|
31
vendor/github.com/edeckers/auroradnsclient/records/datatypes.go
generated
vendored
31
vendor/github.com/edeckers/auroradnsclient/records/datatypes.go
generated
vendored
|
@ -1,31 +0,0 @@
|
|||
package records
|
||||
|
||||
// CreateRecordRequest describes the json payload for creating a record
|
||||
type CreateRecordRequest struct {
|
||||
RecordType string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
// CreateRecordResponse describes the json response for creating a record
|
||||
type CreateRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
RecordType string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
// GetRecordsResponse describes the json response of a single record
|
||||
type GetRecordsResponse struct {
|
||||
ID string `json:"id"`
|
||||
RecordType string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
// RemoveRecordResponse describes the json response for removing a record
|
||||
type RemoveRecordResponse struct {
|
||||
}
|
19
vendor/github.com/edeckers/auroradnsclient/requests/errors/errors.go
generated
vendored
19
vendor/github.com/edeckers/auroradnsclient/requests/errors/errors.go
generated
vendored
|
@ -1,19 +0,0 @@
|
|||
package errors
|
||||
|
||||
// BadRequest HTTP error wrapper
|
||||
type BadRequest error
|
||||
|
||||
// Unauthorized HTTP error wrapper
|
||||
type Unauthorized error
|
||||
|
||||
// Forbidden HTTP error wrapper
|
||||
type Forbidden error
|
||||
|
||||
// NotFound HTTP error wrapper
|
||||
type NotFound error
|
||||
|
||||
// ServerError HTTP error wrapper
|
||||
type ServerError error
|
||||
|
||||
// InvalidStatusCodeError is used when none of the other types applies
|
||||
type InvalidStatusCodeError error
|
124
vendor/github.com/edeckers/auroradnsclient/requests/requestor.go
generated
vendored
124
vendor/github.com/edeckers/auroradnsclient/requests/requestor.go
generated
vendored
|
@ -1,124 +0,0 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
request_errors "github.com/edeckers/auroradnsclient/requests/errors"
|
||||
"github.com/edeckers/auroradnsclient/tokens"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// AuroraRequestor performs actual requests to API
|
||||
type AuroraRequestor struct {
|
||||
endpoint string
|
||||
userID string
|
||||
key string
|
||||
}
|
||||
|
||||
// NewAuroraRequestor instantiates a new requestor
|
||||
func NewAuroraRequestor(endpoint string, userID string, key string) (*AuroraRequestor, error) {
|
||||
if endpoint == "" {
|
||||
return nil, fmt.Errorf("Aurora endpoint missing")
|
||||
}
|
||||
|
||||
if userID == "" || key == "" {
|
||||
return nil, fmt.Errorf("Aurora credentials missing")
|
||||
}
|
||||
|
||||
return &AuroraRequestor{endpoint: endpoint, userID: userID, key: key}, nil
|
||||
}
|
||||
|
||||
func (requestor *AuroraRequestor) buildRequest(relativeURL string, method string, body []byte) (*http.Request, error) {
|
||||
url := fmt.Sprintf("%s/%s", requestor.endpoint, relativeURL)
|
||||
|
||||
request, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to build request: %s", err)
|
||||
|
||||
return request, err
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC()
|
||||
fmtTime := timestamp.Format("20060102T150405Z")
|
||||
|
||||
token := tokens.NewToken(requestor.userID, requestor.key, method, fmt.Sprintf("/%s", relativeURL), timestamp)
|
||||
|
||||
request.Header.Set("X-AuroraDNS-Date", fmtTime)
|
||||
request.Header.Set("Authorization", fmt.Sprintf("AuroraDNSv1 %s", token))
|
||||
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rawRequest, err := httputil.DumpRequestOut(request, true)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to dump request: %s", err)
|
||||
}
|
||||
|
||||
logrus.Debugf("Built request:\n%s", rawRequest)
|
||||
|
||||
return request, err
|
||||
}
|
||||
|
||||
func (requestor *AuroraRequestor) testInvalidResponse(resp *http.Response, response []byte) ([]byte, error) {
|
||||
if resp.StatusCode < 400 {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
logrus.Errorf("Received invalid status code %d:\n%s", resp.StatusCode, response)
|
||||
|
||||
content := errors.New(string(response))
|
||||
|
||||
statusCodeErrorMap := map[int]error{
|
||||
400: request_errors.BadRequest(content),
|
||||
401: request_errors.Unauthorized(content),
|
||||
403: request_errors.Forbidden(content),
|
||||
404: request_errors.NotFound(content),
|
||||
500: request_errors.ServerError(content),
|
||||
}
|
||||
|
||||
mappedError := statusCodeErrorMap[resp.StatusCode]
|
||||
|
||||
if mappedError == nil {
|
||||
return nil, request_errors.InvalidStatusCodeError(content)
|
||||
}
|
||||
|
||||
return nil, mappedError
|
||||
}
|
||||
|
||||
// Request builds and executues a request to the API
|
||||
func (requestor *AuroraRequestor) Request(relativeURL string, method string, body []byte) ([]byte, error) {
|
||||
req, err := requestor.buildRequest(relativeURL, method, body)
|
||||
|
||||
client := http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed request: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
rawResponse, err := httputil.DumpResponse(resp, true)
|
||||
logrus.Debugf("Received raw response:\n%s", rawResponse)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to dump response: %s", err)
|
||||
}
|
||||
|
||||
response, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to read response: %s", response)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err = requestor.testInvalidResponse(resp, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
35
vendor/github.com/edeckers/auroradnsclient/tokens/generator.go
generated
vendored
35
vendor/github.com/edeckers/auroradnsclient/tokens/generator.go
generated
vendored
|
@ -1,35 +0,0 @@
|
|||
package tokens
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// NewToken generates a token for accessing a specific method of the API
|
||||
func NewToken(userID string, key string, method string, action string, timestamp time.Time) string {
|
||||
fmtTime := timestamp.Format("20060102T150405Z")
|
||||
logrus.Debugf("Built timestamp: %s", fmtTime)
|
||||
|
||||
message := strings.Join([]string{method, action, fmtTime}, "")
|
||||
logrus.Debugf("Built message: %s", message)
|
||||
|
||||
signatureHmac := hmac.New(sha256.New, []byte(key))
|
||||
|
||||
signatureHmac.Write([]byte(message))
|
||||
|
||||
signature := base64.StdEncoding.EncodeToString([]byte(signatureHmac.Sum(nil)))
|
||||
logrus.Debugf("Built signature: %s", signature)
|
||||
|
||||
userIDAndSignature := fmt.Sprintf("%s:%s", userID, signature)
|
||||
|
||||
token := base64.StdEncoding.EncodeToString([]byte(userIDAndSignature))
|
||||
logrus.Debugf("Built token: %s", token)
|
||||
|
||||
return token
|
||||
}
|
29
vendor/github.com/edeckers/auroradnsclient/zones.go
generated
vendored
29
vendor/github.com/edeckers/auroradnsclient/zones.go
generated
vendored
|
@ -1,29 +0,0 @@
|
|||
package auroradnsclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/edeckers/auroradnsclient/zones"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetZones returns a list of all zones
|
||||
func (client *AuroraDNSClient) GetZones() ([]zones.ZoneRecord, error) {
|
||||
logrus.Debugf("GetZones")
|
||||
response, err := client.requestor.Request("zones", "GET", []byte(""))
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get zones: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respData []zones.ZoneRecord
|
||||
err = json.Unmarshal(response, &respData)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to unmarshall response: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Unmarshalled response: %+v", respData)
|
||||
return respData, nil
|
||||
}
|
7
vendor/github.com/edeckers/auroradnsclient/zones/datatypes.go
generated
vendored
7
vendor/github.com/edeckers/auroradnsclient/zones/datatypes.go
generated
vendored
|
@ -1,7 +0,0 @@
|
|||
package zones
|
||||
|
||||
// ZoneRecord describes the json format for a zone
|
||||
type ZoneRecord struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
98
vendor/github.com/ldez/go-auroradns/auth.go
generated
vendored
Normal file
98
vendor/github.com/ldez/go-auroradns/auth.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package auroradns
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TokenTransport HTTP transport for API authentication
|
||||
type TokenTransport struct {
|
||||
userID string
|
||||
key string
|
||||
|
||||
// Transport is the underlying HTTP transport to use when making requests.
|
||||
// It will default to http.DefaultTransport if nil.
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// NewTokenTransport Creates a new TokenTransport
|
||||
func NewTokenTransport(userID, key string) (*TokenTransport, error) {
|
||||
if userID == "" || key == "" {
|
||||
return nil, fmt.Errorf("credentials missing")
|
||||
}
|
||||
|
||||
return &TokenTransport{userID: userID, key: key}, nil
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP transaction
|
||||
func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
enrichedReq := &http.Request{}
|
||||
*enrichedReq = *req
|
||||
|
||||
enrichedReq.Header = make(http.Header, len(req.Header))
|
||||
for k, s := range req.Header {
|
||||
enrichedReq.Header[k] = append([]string(nil), s...)
|
||||
}
|
||||
|
||||
if t.userID != "" && t.key != "" {
|
||||
timestamp := time.Now().UTC()
|
||||
|
||||
fmtTime := timestamp.Format("20060102T150405Z")
|
||||
req.Header.Set("X-AuroraDNS-Date", fmtTime)
|
||||
|
||||
token, err := newToken(t.userID, t.key, req.Method, req.URL.Path, timestamp)
|
||||
if err == nil {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("AuroraDNSv1 %s", token))
|
||||
}
|
||||
}
|
||||
|
||||
return t.transport().RoundTrip(enrichedReq)
|
||||
}
|
||||
|
||||
// Wrap Wrap a HTTP client Transport with the TokenTransport
|
||||
func (t *TokenTransport) Wrap(client *http.Client) *http.Client {
|
||||
backup := client.Transport
|
||||
t.Transport = backup
|
||||
client.Transport = t
|
||||
return client
|
||||
}
|
||||
|
||||
// Client Creates a new HTTP client
|
||||
func (t *TokenTransport) Client() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: t,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TokenTransport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// newToken generates a token for accessing a specific method of the API
|
||||
func newToken(userID string, key string, method string, action string, timestamp time.Time) (string, error) {
|
||||
fmtTime := timestamp.Format("20060102T150405Z")
|
||||
message := strings.Join([]string{method, action, fmtTime}, "")
|
||||
|
||||
signatureHmac := hmac.New(sha256.New, []byte(key))
|
||||
_, err := signatureHmac.Write([]byte(message))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signature := base64.StdEncoding.EncodeToString(signatureHmac.Sum(nil))
|
||||
|
||||
userIDAndSignature := fmt.Sprintf("%s:%s", userID, signature)
|
||||
|
||||
token := base64.StdEncoding.EncodeToString([]byte(userIDAndSignature))
|
||||
|
||||
return token, nil
|
||||
}
|
144
vendor/github.com/ldez/go-auroradns/client.go
generated
vendored
Normal file
144
vendor/github.com/ldez/go-auroradns/client.go
generated
vendored
Normal file
|
@ -0,0 +1,144 @@
|
|||
package auroradns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.auroradns.eu"
|
||||
|
||||
const (
|
||||
contentTypeHeader = "Content-Type"
|
||||
contentTypeJSON = "application/json"
|
||||
)
|
||||
|
||||
// ErrorResponse A representation of an API error message.
|
||||
type ErrorResponse struct {
|
||||
ErrorCode string `json:"error"`
|
||||
Message string `json:"errormsg"`
|
||||
}
|
||||
|
||||
func (e *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%s - %s", e.ErrorCode, e.Message)
|
||||
}
|
||||
|
||||
// Option Type of a client option
|
||||
type Option func(*Client) error
|
||||
|
||||
// Client The API client
|
||||
type Client struct {
|
||||
baseURL *url.URL
|
||||
UserAgent string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient Creates a new client
|
||||
func NewClient(httpClient *http.Client, opts ...Option) (*Client, error) {
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
|
||||
baseURL, _ := url.Parse(defaultBaseURL)
|
||||
|
||||
client := &Client{
|
||||
baseURL: baseURL,
|
||||
httpClient: httpClient,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method, resource string, body io.Reader) (*http.Request, error) {
|
||||
u, err := c.baseURL.Parse(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set(contentTypeHeader, contentTypeJSON)
|
||||
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if err = checkResponse(resp); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("failed to read body: %v", err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(raw, v); err != nil {
|
||||
return resp, fmt.Errorf("unmarshaling %T error: %v: %s", err, v, string(raw))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if c := resp.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err == nil && data != nil {
|
||||
errorResponse := new(ErrorResponse)
|
||||
err = json.Unmarshal(data, errorResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling ErrorResponse error: %v: %s", err.Error(), string(data))
|
||||
}
|
||||
|
||||
return errorResponse
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithBaseURL Allows to define a custom base URL
|
||||
func WithBaseURL(rawBaseURL string) func(*Client) error {
|
||||
return func(client *Client) error {
|
||||
if len(rawBaseURL) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(rawBaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client.baseURL = baseURL
|
||||
return nil
|
||||
}
|
||||
}
|
91
vendor/github.com/ldez/go-auroradns/records.go
generated
vendored
Normal file
91
vendor/github.com/ldez/go-auroradns/records.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
package auroradns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Record types
|
||||
const (
|
||||
RecordTypeA = "A"
|
||||
RecordTypeAAAA = "AAAA"
|
||||
RecordTypeCNAME = "CNAME"
|
||||
RecordTypeMX = "MX"
|
||||
RecordTypeNS = "NS"
|
||||
RecordTypeSOA = "SOA"
|
||||
RecordTypeSRV = "SRV"
|
||||
RecordTypeTXT = "TXT"
|
||||
RecordTypeDS = "DS"
|
||||
RecordTypePTR = "PTR"
|
||||
RecordTypeSSHFP = "SSHFP"
|
||||
RecordTypeTLSA = "TLS"
|
||||
)
|
||||
|
||||
// Record a DNS record
|
||||
type Record struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
RecordType string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
// CreateRecord Creates a new record.
|
||||
func (c *Client) CreateRecord(zoneID string, record Record) (*Record, *http.Response, error) {
|
||||
body, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshall request body: %v", err)
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("/zones/%s/records", zoneID)
|
||||
|
||||
req, err := c.newRequest(http.MethodPost, resource, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
newRecord := new(Record)
|
||||
resp, err := c.do(req, newRecord)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return newRecord, resp, nil
|
||||
}
|
||||
|
||||
// DeleteRecord Delete a record.
|
||||
func (c *Client) DeleteRecord(zoneID string, recordID string) (bool, *http.Response, error) {
|
||||
resource := fmt.Sprintf("/zones/%s/records/%s", zoneID, recordID)
|
||||
|
||||
req, err := c.newRequest(http.MethodDelete, resource, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
resp, err := c.do(req, nil)
|
||||
if err != nil {
|
||||
return false, resp, err
|
||||
}
|
||||
|
||||
return true, resp, nil
|
||||
}
|
||||
|
||||
// ListRecords returns a list of all records in given zone
|
||||
func (c *Client) ListRecords(zoneID string) ([]Record, *http.Response, error) {
|
||||
resource := fmt.Sprintf("/zones/%s/records", zoneID)
|
||||
|
||||
req, err := c.newRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var records []Record
|
||||
resp, err := c.do(req, &records)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return records, resp, nil
|
||||
}
|
69
vendor/github.com/ldez/go-auroradns/zones.go
generated
vendored
Normal file
69
vendor/github.com/ldez/go-auroradns/zones.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package auroradns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Zone a DNS zone
|
||||
type Zone struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// CreateZone Creates a zone.
|
||||
func (c *Client) CreateZone(domain string) (*Zone, *http.Response, error) {
|
||||
body, err := json.Marshal(Zone{Name: domain})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshall request body: %v", err)
|
||||
}
|
||||
|
||||
req, err := c.newRequest(http.MethodPost, "/zones", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
zone := new(Zone)
|
||||
resp, err := c.do(req, zone)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return zone, resp, nil
|
||||
}
|
||||
|
||||
// DeleteZone Delete a zone.
|
||||
func (c *Client) DeleteZone(zoneID string) (bool, *http.Response, error) {
|
||||
resource := fmt.Sprintf("/zones/%s", zoneID)
|
||||
|
||||
req, err := c.newRequest(http.MethodDelete, resource, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
resp, err := c.do(req, nil)
|
||||
if err != nil {
|
||||
return false, resp, err
|
||||
}
|
||||
|
||||
return true, resp, nil
|
||||
|
||||
}
|
||||
|
||||
// ListZones returns a list of all zones.
|
||||
func (c *Client) ListZones() ([]Zone, *http.Response, error) {
|
||||
req, err := c.newRequest(http.MethodGet, "/zones", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var zones []Zone
|
||||
resp, err := c.do(req, &zones)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return zones, resp, nil
|
||||
}
|
9
vendor/github.com/xenolf/lego/acme/dns_challenge.go
generated
vendored
9
vendor/github.com/xenolf/lego/acme/dns_challenge.go
generated
vendored
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
@ -18,8 +19,9 @@ type preCheckDNSFunc func(fqdn, value string) (bool, error)
|
|||
var (
|
||||
// PreCheckDNS checks DNS propagation before notifying ACME that
|
||||
// the DNS challenge is ready.
|
||||
PreCheckDNS preCheckDNSFunc = checkDNSPropagation
|
||||
fqdnToZone = map[string]string{}
|
||||
PreCheckDNS preCheckDNSFunc = checkDNSPropagation
|
||||
fqdnToZone = map[string]string{}
|
||||
muFqdnToZone sync.Mutex
|
||||
)
|
||||
|
||||
const defaultResolvConf = "/etc/resolv.conf"
|
||||
|
@ -262,6 +264,9 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
|||
// FindZoneByFqdn determines the zone apex for the given fqdn by recursing up the
|
||||
// domain labels until the nameserver returns a SOA record in the answer section.
|
||||
func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
|
||||
muFqdnToZone.Lock()
|
||||
defer muFqdnToZone.Unlock()
|
||||
|
||||
// Do we have it cached?
|
||||
if zone, ok := fqdnToZone[fqdn]; ok {
|
||||
return zone, nil
|
||||
|
|
45
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
45
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
|
@ -1,3 +1,4 @@
|
|||
// Package auroradns implements a DNS provider for solving the DNS-01 challenge using Aurora DNS.
|
||||
package auroradns
|
||||
|
||||
import (
|
||||
|
@ -6,9 +7,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/edeckers/auroradnsclient"
|
||||
"github.com/edeckers/auroradnsclient/records"
|
||||
"github.com/edeckers/auroradnsclient/zones"
|
||||
"github.com/ldez/go-auroradns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
@ -39,7 +38,7 @@ type DNSProvider struct {
|
|||
recordIDs map[string]string
|
||||
recordIDsMu sync.Mutex
|
||||
config *Config
|
||||
client *auroradnsclient.AuroraDNSClient
|
||||
client *auroradns.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for AuroraDNS.
|
||||
|
@ -85,7 +84,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
client, err := auroradnsclient.NewAuroraDNSClient(config.BaseURL, config.UserID, config.Key)
|
||||
tr, err := auroradns.NewTokenTransport(config.UserID, config.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
||||
client, err := auroradns.NewClient(tr.Client(), auroradns.WithBaseURL(config.BaseURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
@ -117,26 +121,25 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
|
||||
zoneRecord, err := d.getZoneInformationByName(authZone)
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not create record: %v", err)
|
||||
}
|
||||
|
||||
reqData :=
|
||||
records.CreateRecordRequest{
|
||||
RecordType: "TXT",
|
||||
Name: subdomain,
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
record := auroradns.Record{
|
||||
RecordType: "TXT",
|
||||
Name: subdomain,
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
respData, err := d.client.CreateRecord(zoneRecord.ID, reqData)
|
||||
newRecord, _, err := d.client.CreateRecord(zone.ID, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not create record: %v", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = respData.ID
|
||||
d.recordIDs[fqdn] = newRecord.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
|
@ -161,12 +164,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
|
||||
zoneRecord, err := d.getZoneInformationByName(authZone)
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = d.client.RemoveRecord(zoneRecord.ID, recordID)
|
||||
_, _, err = d.client.DeleteRecord(zone.ID, recordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -184,10 +187,10 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRecord, error) {
|
||||
zs, err := d.client.GetZones()
|
||||
func (d *DNSProvider) getZoneInformationByName(name string) (auroradns.Zone, error) {
|
||||
zs, _, err := d.client.ListZones()
|
||||
if err != nil {
|
||||
return zones.ZoneRecord{}, err
|
||||
return auroradns.Zone{}, err
|
||||
}
|
||||
|
||||
for _, element := range zs {
|
||||
|
@ -196,5 +199,5 @@ func (d *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRecord, e
|
|||
}
|
||||
}
|
||||
|
||||
return zones.ZoneRecord{}, fmt.Errorf("could not find Zone record")
|
||||
return auroradns.Zone{}, fmt.Errorf("could not find Zone record")
|
||||
}
|
||||
|
|
165
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
165
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
|
@ -7,6 +7,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -15,18 +16,26 @@ import (
|
|||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultMetadataEndpoint = "http://169.254.169.254"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
SubscriptionID string
|
||||
TenantID string
|
||||
ResourceGroup string
|
||||
// optional if using instance metadata service
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
TenantID string
|
||||
|
||||
SubscriptionID string
|
||||
ResourceGroup string
|
||||
|
||||
MetadataEndpoint string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
|
@ -39,29 +48,26 @@ func NewDefaultConfig() *Config {
|
|||
TTL: env.GetOrDefaultInt("AZURE_TTL", 60),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AZURE_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("AZURE_POLLING_INTERVAL", 2*time.Second),
|
||||
MetadataEndpoint: env.GetOrFile("AZURE_METADATA_ENDPOINT"),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
config *Config
|
||||
authorizer autorest.Authorizer
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for azure.
|
||||
// Credentials must be passed in the environment variables: AZURE_CLIENT_ID,
|
||||
// AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
|
||||
// Credentials can be passed in the environment variables:
|
||||
// AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
|
||||
// If the credentials are _not_ set via the environment,
|
||||
// then it will attempt to get a bearer token via the instance metadata service.
|
||||
// see: https://github.com/Azure/go-autorest/blob/v10.14.0/autorest/azure/auth/auth.go#L38-L42
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.ClientID = values["AZURE_CLIENT_ID"]
|
||||
config.ClientSecret = values["AZURE_CLIENT_SECRET"]
|
||||
config.SubscriptionID = values["AZURE_SUBSCRIPTION_ID"]
|
||||
config.TenantID = values["AZURE_TENANT_ID"]
|
||||
config.ResourceGroup = values["AZURE_RESOURCE_GROUP"]
|
||||
config.SubscriptionID = env.GetOrFile("AZURE_SUBSCRIPTION_ID")
|
||||
config.ResourceGroup = env.GetOrFile("AZURE_RESOURCE_GROUP")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
@ -73,8 +79,8 @@ func NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID,
|
|||
config := NewDefaultConfig()
|
||||
config.ClientID = clientID
|
||||
config.ClientSecret = clientSecret
|
||||
config.SubscriptionID = subscriptionID
|
||||
config.TenantID = tenantID
|
||||
config.SubscriptionID = subscriptionID
|
||||
config.ResourceGroup = resourceGroup
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
|
@ -86,11 +92,40 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
return nil, errors.New("azure: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.ClientID == "" || config.ClientSecret == "" || config.SubscriptionID == "" || config.TenantID == "" || config.ResourceGroup == "" {
|
||||
return nil, errors.New("azure: some credentials information are missing")
|
||||
if config.HTTPClient == nil {
|
||||
config.HTTPClient = http.DefaultClient
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
authorizer, err := getAuthorizer(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.SubscriptionID == "" {
|
||||
subsID, err := getMetadata(config, "subscriptionId")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
if subsID == "" {
|
||||
return nil, errors.New("azure: SubscriptionID is missing")
|
||||
}
|
||||
config.SubscriptionID = subsID
|
||||
}
|
||||
|
||||
if config.ResourceGroup == "" {
|
||||
resGroup, err := getMetadata(config, "resourceGroupName")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
if resGroup == "" {
|
||||
return nil, errors.New("azure: ResourceGroup is missing")
|
||||
}
|
||||
config.ResourceGroup = resGroup
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, authorizer: authorizer}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS
|
||||
|
@ -110,12 +145,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
}
|
||||
|
||||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
spt, err := d.newServicePrincipalToken(azure.PublicCloud.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
|
||||
rec := dns.RecordSet{
|
||||
|
@ -145,12 +175,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
|
||||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
spt, err := d.newServicePrincipalToken(azure.PublicCloud.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
_, err = rsc.Delete(ctx, d.config.ResourceGroup, zone, relative, dns.TXT, "")
|
||||
if err != nil {
|
||||
|
@ -166,14 +191,8 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string,
|
|||
return "", err
|
||||
}
|
||||
|
||||
// Now we want to to Azure and get the zone.
|
||||
spt, err := d.newServicePrincipalToken(azure.PublicCloud.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dc := dns.NewZonesClient(d.config.SubscriptionID)
|
||||
dc.Authorizer = autorest.NewBearerAuthorizer(spt)
|
||||
dc.Authorizer = d.authorizer
|
||||
|
||||
zone, err := dc.Get(ctx, d.config.ResourceGroup, acme.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
|
@ -184,17 +203,61 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string,
|
|||
return to.String(zone.Name), nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the
|
||||
// passed credentials map.
|
||||
func (d *DNSProvider) newServicePrincipalToken(scope string) (*adal.ServicePrincipalToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, d.config.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adal.NewServicePrincipalToken(*oauthConfig, d.config.ClientID, d.config.ClientSecret, scope)
|
||||
}
|
||||
|
||||
// Returns the relative record to the domain
|
||||
func toRelativeRecord(domain, zone string) string {
|
||||
return acme.UnFqdn(strings.TrimSuffix(domain, zone))
|
||||
}
|
||||
|
||||
func getAuthorizer(config *Config) (autorest.Authorizer, error) {
|
||||
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
|
||||
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, config.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(*oauthConfig, config.ClientID, config.ClientSecret, azure.PublicCloud.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.SetSender(config.HTTPClient)
|
||||
return autorest.NewBearerAuthorizer(spt), nil
|
||||
}
|
||||
|
||||
return auth.NewAuthorizerFromEnvironment()
|
||||
}
|
||||
|
||||
// Fetches metadata from environment or he instance metadata service
|
||||
// borrowed from https://github.com/Microsoft/azureimds/blob/master/imdssample.go
|
||||
func getMetadata(config *Config, field string) (string, error) {
|
||||
metadataEndpoint := config.MetadataEndpoint
|
||||
if len(metadataEndpoint) == 0 {
|
||||
metadataEndpoint = defaultMetadataEndpoint
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("%s/metadata/instance/compute/%s", metadataEndpoint, field)
|
||||
req, err := http.NewRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Add("Metadata", "True")
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("format", "text")
|
||||
q.Add("api-version", "2017-12-01")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(respBody[:]), nil
|
||||
}
|
||||
|
|
15
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go
generated
vendored
15
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go
generated
vendored
|
@ -7,6 +7,7 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
@ -27,6 +28,10 @@ type Record struct {
|
|||
SourceID int `json:"sourceId"`
|
||||
}
|
||||
|
||||
type recordsResponse struct {
|
||||
Records *[]Record `json:"data"`
|
||||
}
|
||||
|
||||
// Client DNSMadeEasy client
|
||||
type Client struct {
|
||||
apiKey string
|
||||
|
@ -82,10 +87,6 @@ func (c *Client) GetRecords(domain *Domain, recordName, recordType string) (*[]R
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
type recordsResponse struct {
|
||||
Records *[]Record `json:"data"`
|
||||
}
|
||||
|
||||
records := &recordsResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&records)
|
||||
if err != nil {
|
||||
|
@ -151,7 +152,11 @@ func (c *Client) sendRequest(method, resource string, payload interface{}) (*htt
|
|||
}
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("DNSMadeEasy API request failed with HTTP status code %d", resp.StatusCode)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
|
21
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
21
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
|
@ -1,3 +1,4 @@
|
|||
// Package dnsmadeeasy implements a DNS provider for solving the DNS-01 challenge using DNS Made Easy.
|
||||
package dnsmadeeasy
|
||||
|
||||
import (
|
||||
|
@ -112,13 +113,13 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
|||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.client.GetDomain(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
// create the TXT record
|
||||
|
@ -126,7 +127,10 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
|||
record := &Record{Type: "TXT", Name: name, Value: value, TTL: d.config.TTL}
|
||||
|
||||
err = d.client.CreateRecord(domain, record)
|
||||
return err
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to create record for %s: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT records matching the specified parameters
|
||||
|
@ -135,31 +139,32 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
|||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.client.GetDomain(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
// find matching records
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
records, err := d.client.GetRecords(domain, name, "TXT")
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get records for domain %s: %v", domain.Name, err)
|
||||
}
|
||||
|
||||
// delete records
|
||||
var lastError error
|
||||
for _, record := range *records {
|
||||
err = d.client.DeleteRecord(record)
|
||||
if err != nil {
|
||||
return err
|
||||
lastError = fmt.Errorf("dnsmadeeasy: unable to delete record [id=%d, name=%s]: %v", record.ID, record.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return lastError
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
|
|
2
vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
2
vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
|
@ -1,4 +1,4 @@
|
|||
// Package dreamhost Adds lego support for http://dreamhost.com DNS updates
|
||||
// Package dreamhost implements a DNS provider for solving the DNS-01 challenge using DreamHost.
|
||||
// See https://help.dreamhost.com/hc/en-us/articles/217560167-API_overview
|
||||
// and https://help.dreamhost.com/hc/en-us/articles/217555707-DNS-API-commands for the API spec.
|
||||
package dreamhost
|
||||
|
|
37
vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go
generated
vendored
37
vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go
generated
vendored
|
@ -1,4 +1,4 @@
|
|||
// Package duckdns Adds lego support for http://duckdns.org.
|
||||
// Package duckdns implements a DNS provider for solving the DNS-01 challenge using DuckDNS.
|
||||
// See http://www.duckdns.org/spec.jsp for more info on updating TXT records.
|
||||
package duckdns
|
||||
|
||||
|
@ -7,8 +7,12 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
@ -96,9 +100,16 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
// To update the TXT record we just need to make one simple get request.
|
||||
// In DuckDNS you only have one TXT record shared with the domain and all sub domains.
|
||||
func updateTxtRecord(domain, token, txt string, clear bool) error {
|
||||
u := fmt.Sprintf("https://www.duckdns.org/update?domains=%s&token=%s&clear=%t&txt=%s", domain, token, clear, txt)
|
||||
u, _ := url.Parse("https://www.duckdns.org/update")
|
||||
|
||||
response, err := acme.HTTPClient.Get(u)
|
||||
query := u.Query()
|
||||
query.Set("domains", getMainDomain(domain))
|
||||
query.Set("token", token)
|
||||
query.Set("clear", strconv.FormatBool(clear))
|
||||
query.Set("txt", txt)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
response, err := acme.HTTPClient.Get(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -115,3 +126,23 @@ func updateTxtRecord(domain, token, txt string, clear bool) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DuckDNS only lets you write to your subdomain
|
||||
// so it must be in format subdomain.duckdns.org
|
||||
// not in format subsubdomain.subdomain.duckdns.org
|
||||
// so strip off everything that is not top 3 levels
|
||||
func getMainDomain(domain string) string {
|
||||
domain = acme.UnFqdn(domain)
|
||||
|
||||
split := dns.Split(domain)
|
||||
if strings.HasSuffix(strings.ToLower(domain), "duckdns.org") {
|
||||
if len(split) < 3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
firstSubDomainIndex := split[len(split)-3]
|
||||
return domain[firstSubDomainIndex:]
|
||||
}
|
||||
|
||||
return domain[split[len(split)-1]:]
|
||||
}
|
||||
|
|
229
vendor/github.com/xenolf/lego/providers/dns/netcup/client.go
generated
vendored
229
vendor/github.com/xenolf/lego/providers/dns/netcup/client.go
generated
vendored
|
@ -25,27 +25,27 @@ type Request struct {
|
|||
Param interface{} `json:"param"`
|
||||
}
|
||||
|
||||
// LoginMsg as specified in netcup WSDL
|
||||
// LoginRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#login
|
||||
type LoginMsg struct {
|
||||
type LoginRequest struct {
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APIPassword string `json:"apipassword"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// LogoutMsg as specified in netcup WSDL
|
||||
// LogoutRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#logout
|
||||
type LogoutMsg struct {
|
||||
type LogoutRequest struct {
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APISessionID string `json:"apisessionid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateDNSRecordsMsg as specified in netcup WSDL
|
||||
// UpdateDNSRecordsRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#updateDnsRecords
|
||||
type UpdateDNSRecordsMsg struct {
|
||||
type UpdateDNSRecordsRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
|
@ -55,15 +55,15 @@ type UpdateDNSRecordsMsg struct {
|
|||
}
|
||||
|
||||
// DNSRecordSet as specified in netcup WSDL
|
||||
// needed in UpdateDNSRecordsMsg
|
||||
// needed in UpdateDNSRecordsRequest
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecordset
|
||||
type DNSRecordSet struct {
|
||||
DNSRecords []DNSRecord `json:"dnsrecords"`
|
||||
}
|
||||
|
||||
// InfoDNSRecordsMsg as specified in netcup WSDL
|
||||
// InfoDNSRecordsRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#infoDnsRecords
|
||||
type InfoDNSRecordsMsg struct {
|
||||
type InfoDNSRecordsRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
|
@ -87,33 +87,30 @@ type DNSRecord struct {
|
|||
// ResponseMsg as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Responsemessage
|
||||
type ResponseMsg struct {
|
||||
ServerRequestID string `json:"serverrequestid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statuscode"`
|
||||
ShortMessage string `json:"shortmessage"`
|
||||
LongMessage string `json:"longmessage"`
|
||||
ResponseData ResponseData `json:"responsedata,omitempty"`
|
||||
ServerRequestID string `json:"serverrequestid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statuscode"`
|
||||
ShortMessage string `json:"shortmessage"`
|
||||
LongMessage string `json:"longmessage"`
|
||||
ResponseData json.RawMessage `json:"responsedata,omitempty"`
|
||||
}
|
||||
|
||||
// LogoutResponseMsg similar to ResponseMsg
|
||||
// allows empty ResponseData field whilst unmarshaling
|
||||
type LogoutResponseMsg struct {
|
||||
ServerRequestID string `json:"serverrequestid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statuscode"`
|
||||
ShortMessage string `json:"shortmessage"`
|
||||
LongMessage string `json:"longmessage"`
|
||||
ResponseData string `json:"responsedata,omitempty"`
|
||||
func (r *ResponseMsg) Error() string {
|
||||
return fmt.Sprintf("an error occurred during the action %s: [Status=%s, StatusCode=%d, ShortMessage=%s, LongMessage=%s]",
|
||||
r.Action, r.Status, r.StatusCode, r.ShortMessage, r.LongMessage)
|
||||
}
|
||||
|
||||
// ResponseData to enable correct unmarshaling of ResponseMsg
|
||||
type ResponseData struct {
|
||||
// LoginResponse response to login action.
|
||||
type LoginResponse struct {
|
||||
APISessionID string `json:"apisessionid"`
|
||||
}
|
||||
|
||||
// InfoDNSRecordsResponse response to infoDnsRecords action.
|
||||
type InfoDNSRecordsResponse struct {
|
||||
APISessionID string `json:"apisessionid"`
|
||||
DNSRecords []DNSRecord `json:"dnsrecords"`
|
||||
DNSRecords []DNSRecord `json:"dnsrecords,omitempty"`
|
||||
}
|
||||
|
||||
// Client netcup DNS client
|
||||
|
@ -126,7 +123,11 @@ type Client struct {
|
|||
}
|
||||
|
||||
// NewClient creates a netcup DNS client
|
||||
func NewClient(customerNumber string, apiKey string, apiPassword string) *Client {
|
||||
func NewClient(customerNumber string, apiKey string, apiPassword string) (*Client, error) {
|
||||
if customerNumber == "" || apiKey == "" || apiPassword == "" {
|
||||
return nil, fmt.Errorf("credentials missing")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
customerNumber: customerNumber,
|
||||
apiKey: apiKey,
|
||||
|
@ -135,7 +136,7 @@ func NewClient(customerNumber string, apiKey string, apiPassword string) *Client
|
|||
HTTPClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Login performs the login as specified by the netcup WSDL
|
||||
|
@ -144,7 +145,7 @@ func NewClient(customerNumber string, apiKey string, apiPassword string) *Client
|
|||
func (c *Client) Login() (string, error) {
|
||||
payload := &Request{
|
||||
Action: "login",
|
||||
Param: &LoginMsg{
|
||||
Param: &LoginRequest{
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APIPassword: c.apiPassword,
|
||||
|
@ -152,21 +153,13 @@ func (c *Client) Login() (string, error) {
|
|||
},
|
||||
}
|
||||
|
||||
response, err := c.sendRequest(payload)
|
||||
var responseData LoginResponse
|
||||
err := c.doRequest(payload, &responseData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error sending request to DNS-API, %v", err)
|
||||
return "", fmt.Errorf("loging error: %v", err)
|
||||
}
|
||||
|
||||
var r ResponseMsg
|
||||
|
||||
err = json.Unmarshal(response, &r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error decoding response of DNS-API, %v", err)
|
||||
}
|
||||
if r.Status != success {
|
||||
return "", fmt.Errorf("error logging into DNS-API, %v", r.LongMessage)
|
||||
}
|
||||
return r.ResponseData.APISessionID, nil
|
||||
return responseData.APISessionID, nil
|
||||
}
|
||||
|
||||
// Logout performs the logout with the supplied sessionID as specified by the netcup WSDL
|
||||
|
@ -174,7 +167,7 @@ func (c *Client) Login() (string, error) {
|
|||
func (c *Client) Logout(sessionID string) error {
|
||||
payload := &Request{
|
||||
Action: "logout",
|
||||
Param: &LogoutMsg{
|
||||
Param: &LogoutRequest{
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: sessionID,
|
||||
|
@ -182,54 +175,34 @@ func (c *Client) Logout(sessionID string) error {
|
|||
},
|
||||
}
|
||||
|
||||
response, err := c.sendRequest(payload)
|
||||
err := c.doRequest(payload, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error logging out of DNS-API: %v", err)
|
||||
return fmt.Errorf("logout error: %v", err)
|
||||
}
|
||||
|
||||
var r LogoutResponseMsg
|
||||
|
||||
err = json.Unmarshal(response, &r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error logging out of DNS-API: %v", err)
|
||||
}
|
||||
|
||||
if r.Status != success {
|
||||
return fmt.Errorf("error logging out of DNS-API: %v", r.ShortMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDNSRecord performs an update of the DNSRecords as specified by the netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) UpdateDNSRecord(sessionID, domainName string, record DNSRecord) error {
|
||||
func (c *Client) UpdateDNSRecord(sessionID, domainName string, records []DNSRecord) error {
|
||||
payload := &Request{
|
||||
Action: "updateDnsRecords",
|
||||
Param: UpdateDNSRecordsMsg{
|
||||
Param: UpdateDNSRecordsRequest{
|
||||
DomainName: domainName,
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: sessionID,
|
||||
ClientRequestID: "",
|
||||
DNSRecordSet: DNSRecordSet{DNSRecords: []DNSRecord{record}},
|
||||
DNSRecordSet: DNSRecordSet{DNSRecords: records},
|
||||
},
|
||||
}
|
||||
|
||||
response, err := c.sendRequest(payload)
|
||||
err := c.doRequest(payload, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error when sending the request: %v", err)
|
||||
}
|
||||
|
||||
var r ResponseMsg
|
||||
|
||||
err = json.Unmarshal(response, &r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Status != success {
|
||||
return fmt.Errorf("%s: %+v", r.ShortMessage, r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -239,7 +212,7 @@ func (c *Client) UpdateDNSRecord(sessionID, domainName string, record DNSRecord)
|
|||
func (c *Client) GetDNSRecords(hostname, apiSessionID string) ([]DNSRecord, error) {
|
||||
payload := &Request{
|
||||
Action: "infoDnsRecords",
|
||||
Param: InfoDNSRecordsMsg{
|
||||
Param: InfoDNSRecordsRequest{
|
||||
DomainName: hostname,
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
|
@ -248,82 +221,98 @@ func (c *Client) GetDNSRecords(hostname, apiSessionID string) ([]DNSRecord, erro
|
|||
},
|
||||
}
|
||||
|
||||
response, err := c.sendRequest(payload)
|
||||
var responseData InfoDNSRecordsResponse
|
||||
err := c.doRequest(payload, &responseData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error when sending the request: %v", err)
|
||||
}
|
||||
|
||||
var r ResponseMsg
|
||||
|
||||
err = json.Unmarshal(response, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.Status != success {
|
||||
return nil, fmt.Errorf("%s", r.ShortMessage)
|
||||
}
|
||||
return r.ResponseData.DNSRecords, nil
|
||||
return responseData.DNSRecords, nil
|
||||
|
||||
}
|
||||
|
||||
// sendRequest marshals given body to JSON, send the request to netcup API
|
||||
// doRequest marshals given body to JSON, send the request to netcup API
|
||||
// and returns body of response
|
||||
func (c *Client) sendRequest(payload interface{}) ([]byte, error) {
|
||||
func (c *Client) doRequest(payload interface{}, responseData interface{}) error {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, c.BaseURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
req.Close = true
|
||||
|
||||
req.Close = true
|
||||
req.Header.Set("content-type", "application/json")
|
||||
req.Header.Set("User-Agent", acme.UserAgent)
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("API request failed with HTTP Status code %d", resp.StatusCode)
|
||||
if err = checkResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
respMsg, err := decodeResponseMsg(resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read of response body failed, %v", err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return body, nil
|
||||
}
|
||||
if respMsg.Status != success {
|
||||
return respMsg
|
||||
}
|
||||
|
||||
// GetDNSRecordIdx searches a given array of DNSRecords for a given DNSRecord
|
||||
// equivalence is determined by Destination and RecortType attributes
|
||||
// returns index of given DNSRecord in given array of DNSRecords
|
||||
func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) {
|
||||
for index, element := range records {
|
||||
if record.Destination == element.Destination && record.RecordType == element.RecordType {
|
||||
return index, nil
|
||||
if responseData != nil {
|
||||
err = json.Unmarshal(respMsg.ResponseData, responseData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: unmarshaling %T error: %v: %s",
|
||||
respMsg, responseData, err, string(respMsg.ResponseData))
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("no DNS Record found")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTxtRecord uses the supplied values to return a DNSRecord of type TXT for the dns-01 challenge
|
||||
func CreateTxtRecord(hostname, value string, ttl int) DNSRecord {
|
||||
return DNSRecord{
|
||||
ID: 0,
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Priority: "",
|
||||
Destination: value,
|
||||
DeleteRecord: false,
|
||||
State: "",
|
||||
TTL: ttl,
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode > 299 {
|
||||
if resp.Body == nil {
|
||||
return fmt.Errorf("response body is nil, status code=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("status code=%d: %s", resp.StatusCode, string(raw))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeResponseMsg(resp *http.Response) (*ResponseMsg, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil, status code=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
var respMsg ResponseMsg
|
||||
err = json.Unmarshal(raw, &respMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling %T error [status code=%d]: %v: %s", respMsg, resp.StatusCode, err, string(raw))
|
||||
}
|
||||
|
||||
return &respMsg, nil
|
||||
}
|
||||
|
|
93
vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go
generated
vendored
93
vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go
generated
vendored
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -27,8 +28,8 @@ type Config struct {
|
|||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NETCUP_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NETCUP_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NETCUP_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NETCUP_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("NETCUP_POLLING_INTERVAL", 5*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NETCUP_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
|
@ -76,11 +77,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
return nil, errors.New("netcup: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Customer == "" || config.Key == "" || config.Password == "" {
|
||||
return nil, fmt.Errorf("netcup: netcup credentials missing")
|
||||
client, err := NewClient(config.Customer, config.Key, config.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
client := NewClient(config.Customer, config.Key, config.Password)
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
|
@ -100,27 +101,37 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
|||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
record := CreateTxtRecord(hostname, value, d.config.TTL)
|
||||
|
||||
err = d.client.UpdateDNSRecord(sessionID, acme.UnFqdn(zone), record)
|
||||
if err != nil {
|
||||
if errLogout := d.client.Logout(sessionID); errLogout != nil {
|
||||
return fmt.Errorf("netcup: failed to add TXT-Record: %v; %v", err, errLogout)
|
||||
defer func() {
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
log.Print("netcup: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
record := createTxtRecord(hostname, value, d.config.TTL)
|
||||
|
||||
zone = acme.UnFqdn(zone)
|
||||
|
||||
records, err := d.client.GetDNSRecords(zone, sessionID)
|
||||
if err != nil {
|
||||
// skip no existing records
|
||||
log.Infof("no existing records, error ignored: %v", err)
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to add TXT-Record: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domainname, keyAuth)
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domainName, keyAuth)
|
||||
|
||||
zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
|
@ -132,6 +143,13 @@ func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error {
|
|||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
log.Print("netcup: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
|
||||
zone = acme.UnFqdn(zone)
|
||||
|
@ -141,27 +159,20 @@ func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error {
|
|||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
record := CreateTxtRecord(hostname, value, 0)
|
||||
record := createTxtRecord(hostname, value, 0)
|
||||
|
||||
idx, err := GetDNSRecordIdx(records, record)
|
||||
idx, err := getDNSRecordIdx(records, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
records[idx].DeleteRecord = true
|
||||
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, records[idx])
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, []DNSRecord{records[idx]})
|
||||
if err != nil {
|
||||
if errLogout := d.client.Logout(sessionID); errLogout != nil {
|
||||
return fmt.Errorf("netcup: %v; %v", err, errLogout)
|
||||
}
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -170,3 +181,29 @@ func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error {
|
|||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// getDNSRecordIdx searches a given array of DNSRecords for a given DNSRecord
|
||||
// equivalence is determined by Destination and RecortType attributes
|
||||
// returns index of given DNSRecord in given array of DNSRecords
|
||||
func getDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) {
|
||||
for index, element := range records {
|
||||
if record.Destination == element.Destination && record.RecordType == element.RecordType {
|
||||
return index, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("no DNS Record found")
|
||||
}
|
||||
|
||||
// createTxtRecord uses the supplied values to return a DNSRecord of type TXT for the dns-01 challenge
|
||||
func createTxtRecord(hostname, value string, ttl int) DNSRecord {
|
||||
return DNSRecord{
|
||||
ID: 0,
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Priority: "",
|
||||
Destination: value,
|
||||
DeleteRecord: false,
|
||||
State: "",
|
||||
TTL: ttl,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue