mirror of
				https://github.com/minio/minio.git
				synced 2025-10-31 16:21:49 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			239 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|  * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
 | |
|  *
 | |
|  * 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.
 | |
|  */
 | |
| 
 | |
| package dns
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/tls"
 | |
| 	"crypto/x509"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/dgrijalva/jwt-go"
 | |
| 	"github.com/minio/minio/cmd/config"
 | |
| 	xhttp "github.com/minio/minio/cmd/http"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	defaultOperatorContextTimeout = 10 * time.Second
 | |
| 	// ErrNotImplemented - Indicates the functionality which is not implemented
 | |
| 	ErrNotImplemented = errors.New("The method is not implemented")
 | |
| )
 | |
| 
 | |
| func (c *OperatorDNS) addAuthHeader(r *http.Request) error {
 | |
| 	if c.username == "" || c.password == "" {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	claims := &jwt.StandardClaims{
 | |
| 		ExpiresAt: int64(15 * time.Minute),
 | |
| 		Issuer:    c.username,
 | |
| 		Subject:   config.EnvDNSWebhook,
 | |
| 	}
 | |
| 
 | |
| 	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
 | |
| 	ss, err := token.SignedString([]byte(c.password))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	r.Header.Set("Authorization", "Bearer "+ss)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *OperatorDNS) endpoint(bucket string, delete bool) (string, error) {
 | |
| 	u, err := url.Parse(c.Endpoint)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	q := u.Query()
 | |
| 	q.Add("bucket", bucket)
 | |
| 	q.Add("delete", strconv.FormatBool(delete))
 | |
| 	u.RawQuery = q.Encode()
 | |
| 	return u.String(), nil
 | |
| }
 | |
| 
 | |
| // Put - Adds DNS entries into operator webhook server
 | |
| func (c *OperatorDNS) Put(bucket string) error {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), defaultOperatorContextTimeout)
 | |
| 	defer cancel()
 | |
| 	e, err := c.endpoint(bucket, false)
 | |
| 	if err != nil {
 | |
| 		return newError(bucket, err)
 | |
| 	}
 | |
| 	req, err := http.NewRequestWithContext(ctx, http.MethodPost, e, nil)
 | |
| 	if err != nil {
 | |
| 		return newError(bucket, err)
 | |
| 	}
 | |
| 	if err = c.addAuthHeader(req); err != nil {
 | |
| 		return newError(bucket, err)
 | |
| 	}
 | |
| 	resp, err := c.httpClient.Do(req)
 | |
| 	if err != nil {
 | |
| 		if derr := c.Delete(bucket); derr != nil {
 | |
| 			return newError(bucket, derr)
 | |
| 		}
 | |
| 	}
 | |
| 	var errorStringBuilder strings.Builder
 | |
| 	io.Copy(&errorStringBuilder, io.LimitReader(resp.Body, resp.ContentLength))
 | |
| 	xhttp.DrainBody(resp.Body)
 | |
| 	if resp.StatusCode != http.StatusOK {
 | |
| 		errorString := errorStringBuilder.String()
 | |
| 		switch resp.StatusCode {
 | |
| 		case http.StatusConflict:
 | |
| 			return ErrBucketConflict(Error{bucket, errors.New(errorString)})
 | |
| 		}
 | |
| 		return newError(bucket, fmt.Errorf("service create for bucket %s, failed with status %s, error %s", bucket, resp.Status, errorString))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func newError(bucket string, err error) error {
 | |
| 	e := Error{bucket, err}
 | |
| 	if strings.Contains(err.Error(), "invalid bucket name") {
 | |
| 		return ErrInvalidBucketName(e)
 | |
| 	}
 | |
| 	return e
 | |
| }
 | |
| 
 | |
| // Delete - Removes DNS entries added in Put().
 | |
| func (c *OperatorDNS) Delete(bucket string) error {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), defaultOperatorContextTimeout)
 | |
| 	defer cancel()
 | |
| 	e, err := c.endpoint(bucket, true)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	req, err := http.NewRequestWithContext(ctx, http.MethodPost, e, nil)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err = c.addAuthHeader(req); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	resp, err := c.httpClient.Do(req)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	xhttp.DrainBody(resp.Body)
 | |
| 	if resp.StatusCode != http.StatusOK {
 | |
| 		return fmt.Errorf("request to delete the service for bucket %s, failed with status %s", bucket, resp.Status)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // DeleteRecord - Removes a specific DNS entry
 | |
| // No Op for Operator because operator deals on with bucket entries
 | |
| func (c *OperatorDNS) DeleteRecord(record SrvRecord) error {
 | |
| 	return ErrNotImplemented
 | |
| }
 | |
| 
 | |
| // Close closes the internal http client
 | |
| func (c *OperatorDNS) Close() error {
 | |
| 	c.httpClient.CloseIdleConnections()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // List - Retrieves list of DNS entries for the domain.
 | |
| // This is a No Op for Operator because, there is no intent to enforce global
 | |
| // namespace at MinIO level with this DNS entry. The global namespace in
 | |
| // enforced by the Kubernetes Operator
 | |
| func (c *OperatorDNS) List() (srvRecords map[string][]SrvRecord, err error) {
 | |
| 	return nil, ErrNotImplemented
 | |
| }
 | |
| 
 | |
| // Get - Retrieves DNS records for a bucket.
 | |
| // This is a No Op for Operator because, there is no intent to enforce global
 | |
| // namespace at MinIO level with this DNS entry. The global namespace in
 | |
| // enforced by the Kubernetes Operator
 | |
| func (c *OperatorDNS) Get(bucket string) (srvRecords []SrvRecord, err error) {
 | |
| 	return nil, ErrNotImplemented
 | |
| }
 | |
| 
 | |
| // String stringer name for this implementation of dns.Store
 | |
| func (c *OperatorDNS) String() string {
 | |
| 	return "webhookDNS"
 | |
| }
 | |
| 
 | |
| // OperatorDNS - represents dns config for MinIO k8s operator.
 | |
| type OperatorDNS struct {
 | |
| 	httpClient *http.Client
 | |
| 	Endpoint   string
 | |
| 	rootCAs    *x509.CertPool
 | |
| 	username   string
 | |
| 	password   string
 | |
| }
 | |
| 
 | |
| // OperatorOption - functional options pattern style for OperatorDNS
 | |
| type OperatorOption func(*OperatorDNS)
 | |
| 
 | |
| // Authentication - custom username and password for authenticating at the endpoint
 | |
| func Authentication(username, password string) OperatorOption {
 | |
| 	return func(args *OperatorDNS) {
 | |
| 		args.username = username
 | |
| 		args.password = password
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // RootCAs - add custom trust certs pool
 | |
| func RootCAs(CAs *x509.CertPool) OperatorOption {
 | |
| 	return func(args *OperatorDNS) {
 | |
| 		args.rootCAs = CAs
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // NewOperatorDNS - initialize a new K8S Operator DNS set/unset values.
 | |
| func NewOperatorDNS(endpoint string, setters ...OperatorOption) (Store, error) {
 | |
| 	if endpoint == "" {
 | |
| 		return nil, errors.New("invalid argument")
 | |
| 	}
 | |
| 
 | |
| 	args := &OperatorDNS{
 | |
| 		Endpoint: endpoint,
 | |
| 	}
 | |
| 	for _, setter := range setters {
 | |
| 		setter(args)
 | |
| 	}
 | |
| 	args.httpClient = &http.Client{
 | |
| 		Transport: &http.Transport{
 | |
| 			Proxy: http.ProxyFromEnvironment,
 | |
| 			DialContext: (&net.Dialer{
 | |
| 				Timeout:   3 * time.Second,
 | |
| 				KeepAlive: 5 * time.Second,
 | |
| 			}).DialContext,
 | |
| 			ResponseHeaderTimeout: 3 * time.Second,
 | |
| 			TLSHandshakeTimeout:   3 * time.Second,
 | |
| 			ExpectContinueTimeout: 3 * time.Second,
 | |
| 			TLSClientConfig: &tls.Config{
 | |
| 				RootCAs: args.rootCAs,
 | |
| 			},
 | |
| 			// Go net/http automatically unzip if content-type is
 | |
| 			// gzip disable this feature, as we are always interested
 | |
| 			// in raw stream.
 | |
| 			DisableCompression: true,
 | |
| 		},
 | |
| 	}
 | |
| 	return args, nil
 | |
| }
 |