github.com/Venafi/vcert/v5@v5.10.2/pkg/venafi/cloud/search.go (about)

     1  /*
     2   * Copyright 2018 Venafi, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *  http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cloud
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"log"
    24  	"math"
    25  	"net/http"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/Venafi/vcert/v5/pkg/certificate"
    30  )
    31  
    32  type SearchRequest struct {
    33  	Expression *Expression  `json:"expression"`
    34  	Ordering   *interface{} `json:"ordering,omitempty"`
    35  	Paging     *Paging      `json:"paging,omitempty"`
    36  	// ordering is not used here so far
    37  	// "ordering": {"orders": [{"direction": "ASC", "field": "subjectCN"},{"direction": "DESC", "field": "keyStrength"}]},
    38  }
    39  
    40  type Expression struct {
    41  	Operator Operator  `json:"operator,omitempty"`
    42  	Operands []Operand `json:"operands,omitempty"`
    43  }
    44  
    45  type Operand struct {
    46  	Field    Field       `json:"field"`
    47  	Operator Operator    `json:"operator"`
    48  	Value    interface{} `json:"value,omitempty"`
    49  	Values   interface{} `json:"values,omitempty"`
    50  }
    51  
    52  type Field string
    53  type Operator string
    54  
    55  type Paging struct {
    56  	PageNumber int `json:"pageNumber"`
    57  	PageSize   int `json:"pageSize"`
    58  }
    59  
    60  const (
    61  	EQ    Operator = "EQ"
    62  	FIND  Operator = "FIND"
    63  	GT    Operator = "GT"
    64  	GTE   Operator = "GTE"
    65  	IN    Operator = "IN"
    66  	LT    Operator = "LT"
    67  	LTE   Operator = "LTE"
    68  	MATCH Operator = "MATCH"
    69  	AND   Operator = "AND"
    70  )
    71  
    72  type CertificateSearchResponse struct {
    73  	Count        int           `json:"count"`
    74  	Certificates []Certificate `json:"certificates"`
    75  }
    76  
    77  type Certificate struct {
    78  	Id                            string              `json:"id"`
    79  	ManagedCertificateId          string              `json:"managedCertificateId"`
    80  	CertificateRequestId          string              `json:"certificateRequestId"`
    81  	SubjectCN                     []string            `json:"subjectCN"`
    82  	SubjectAlternativeNamesByType map[string][]string `json:"subjectAlternativeNamesByType"`
    83  	SerialNumber                  string              `json:"serialNumber"`
    84  	Fingerprint                   string              `json:"fingerprint"`
    85  	ValidityStart                 string              `json:"validityStart"`
    86  	ValidityEnd                   string              `json:"validityEnd"`
    87  	ApplicationIds                []string            `json:"applicationIds"`
    88  	/* ... and many more fields ... */
    89  }
    90  
    91  func (c Certificate) ToCertificateInfo() certificate.CertificateInfo {
    92  	var cn string
    93  	if len(c.SubjectCN) > 0 {
    94  		cn = c.SubjectCN[0]
    95  	}
    96  
    97  	start, err := time.Parse(time.RFC3339, c.ValidityStart)
    98  	if err != nil { //we just print the error, and let the user know.
    99  		log.Println(err)
   100  	}
   101  
   102  	end, err := time.Parse(time.RFC3339, c.ValidityEnd)
   103  	if err != nil { //we just print the error, and let the user know.
   104  		log.Println(err)
   105  	}
   106  
   107  	return certificate.CertificateInfo{
   108  		ID: c.Id,
   109  		CN: cn,
   110  		SANS: certificate.Sans{
   111  			DNS:   c.SubjectAlternativeNamesByType["dNSName"],
   112  			Email: c.SubjectAlternativeNamesByType["rfc822Name"],
   113  			IP:    c.SubjectAlternativeNamesByType["iPAddress"],
   114  			URI:   c.SubjectAlternativeNamesByType["uniformResourceIdentifier"],
   115  			// currently not supported on VaaS
   116  			// UPN: cert.SubjectAlternativeNamesByType["x400Address"],
   117  		},
   118  		Serial:     c.SerialNumber,
   119  		Thumbprint: c.Fingerprint,
   120  		ValidFrom:  start,
   121  		ValidTo:    end,
   122  	}
   123  }
   124  
   125  func ParseCertificateSearchResponse(httpStatusCode int, body []byte) (searchResult *CertificateSearchResponse, err error) {
   126  	switch httpStatusCode {
   127  	case http.StatusOK:
   128  		var searchResult = &CertificateSearchResponse{}
   129  		err = json.Unmarshal(body, searchResult)
   130  		if err != nil {
   131  			return nil, fmt.Errorf("failed to parse search results: %s, body: %s", err, body)
   132  		}
   133  		return searchResult, nil
   134  	default:
   135  		if body != nil {
   136  			respErrors, err := parseResponseErrors(body)
   137  			if err == nil {
   138  				respError := fmt.Sprintf("Unexpected status code on Venafi Cloud certificate search. Status: %d\n", httpStatusCode)
   139  				for _, e := range respErrors {
   140  					respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message)
   141  				}
   142  				return nil, errors.New(respError)
   143  			}
   144  		}
   145  		return nil, fmt.Errorf("unexpected status code on Venafi Cloud certificate search. Status: %d", httpStatusCode)
   146  	}
   147  }
   148  
   149  // returns everything up to the last slash (if any)
   150  //
   151  // example:
   152  // Just The App Name
   153  // -> Just The App Name
   154  //
   155  // The application\\With Cit
   156  // -> The application
   157  //
   158  // The complex application\\name\\and the cit
   159  // -> The complex application\\name
   160  func getAppNameFromZone(zone string) string {
   161  	lastSlash := strings.LastIndex(zone, "\\")
   162  
   163  	// there is no backslash in zone, meaning it's just the application name,
   164  	// return it
   165  	if lastSlash == -1 {
   166  		return zone
   167  	}
   168  
   169  	return zone[:lastSlash]
   170  }
   171  
   172  // TODO: test this function
   173  func formatSearchCertificateArguments(cn string, sans *certificate.Sans, certMinTimeLeft time.Duration) *SearchRequest {
   174  	// convert a time.Duration to days
   175  	certMinTimeDays := math.Floor(certMinTimeLeft.Hours() / 24)
   176  
   177  	// generate base request
   178  	req := &SearchRequest{
   179  		Expression: &Expression{
   180  			Operator: AND,
   181  			Operands: []Operand{
   182  				{
   183  					Field:    "validityPeriodDays",
   184  					Operator: GTE,
   185  					Value:    &certMinTimeDays,
   186  				},
   187  			},
   188  		},
   189  	}
   190  
   191  	if sans != nil && sans.DNS != nil {
   192  		addOperand(req, Operand{
   193  			Field:    "subjectAlternativeNameDns",
   194  			Operator: IN,
   195  			Values:   sans.DNS,
   196  		})
   197  	}
   198  
   199  	// only if a CN is provided, we add the field to the search request
   200  	if cn != "" {
   201  		addOperand(req, Operand{
   202  			Field:    "subjectCN",
   203  			Operator: EQ,
   204  			Value:    &cn,
   205  		})
   206  	}
   207  
   208  	return req
   209  }
   210  
   211  func addOperand(req *SearchRequest, o Operand) *SearchRequest {
   212  	req.Expression.Operands = append(req.Expression.Operands, o)
   213  	return req
   214  }