github.com/akamai/AkamaiOPEN-edgegrid-golang/v2@v2.17.0/pkg/cps/enrollments.go (about)

     1  package cps
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  
    11  	validation "github.com/go-ozzo/ozzo-validation/v4"
    12  )
    13  
    14  type (
    15  	// Enrollments is a CPS enrollments API interface
    16  	Enrollments interface {
    17  		// ListEnrollments fetches all enrollments with given contractId
    18  		//
    19  		// See https://techdocs.akamai.com/cps/reference/get-enrollments
    20  		ListEnrollments(context.Context, ListEnrollmentsRequest) (*ListEnrollmentsResponse, error)
    21  
    22  		// GetEnrollment fetches enrollment object with given ID
    23  		//
    24  		// See: https://developer.akamai.com/api/core_features/certificate_provisioning_system/v2.html#getasingleenrollment
    25  		GetEnrollment(context.Context, GetEnrollmentRequest) (*Enrollment, error)
    26  
    27  		// CreateEnrollment creates a new enrollment
    28  		//
    29  		// See: https://developer.akamai.com/api/core_features/certificate_provisioning_system/v2.html#postenrollments
    30  		CreateEnrollment(context.Context, CreateEnrollmentRequest) (*CreateEnrollmentResponse, error)
    31  
    32  		// UpdateEnrollment updates a single enrollment entry with given ID
    33  		//
    34  		// See: https://developer.akamai.com/api/core_features/certificate_provisioning_system/v2.html#putasingleenrollment
    35  		UpdateEnrollment(context.Context, UpdateEnrollmentRequest) (*UpdateEnrollmentResponse, error)
    36  
    37  		// RemoveEnrollment removes an enrollment with given ID
    38  		//
    39  		// See: https://developer.akamai.com/api/core_features/certificate_provisioning_system/v2.html#deleteasingleenrollment
    40  		RemoveEnrollment(context.Context, RemoveEnrollmentRequest) (*RemoveEnrollmentResponse, error)
    41  	}
    42  
    43  	// ListEnrollmentsResponse represents list of CPS enrollment objects under given contractId. It is used as a response body while fetching enrollments by contractId
    44  	ListEnrollmentsResponse struct {
    45  		Enrollments []Enrollment `json:"enrollments"`
    46  	}
    47  
    48  	// Enrollment represents a CPS enrollment object. It is used both as a request body for enrollment creation and response body while fetching enrollment by ID
    49  	Enrollment struct {
    50  		AdminContact                   *Contact              `json:"adminContact"`
    51  		AutoRenewalStartTime           string                `json:"autoRenewalStartTime,omitempty"`
    52  		CertificateChainType           string                `json:"certificateChainType,omitempty"`
    53  		CertificateType                string                `json:"certificateType"`
    54  		ChangeManagement               bool                  `json:"changeManagement"`
    55  		CSR                            *CSR                  `json:"csr"`
    56  		EnableMultiStackedCertificates bool                  `json:"enableMultiStackedCertificates"`
    57  		Location                       string                `json:"location,omitempty"`
    58  		MaxAllowedSanNames             int                   `json:"maxAllowedSanNames,omitempty"`
    59  		MaxAllowedWildcardSanNames     int                   `json:"maxAllowedWildcardSanNames,omitempty"`
    60  		NetworkConfiguration           *NetworkConfiguration `json:"networkConfiguration"`
    61  		Org                            *Org                  `json:"org"`
    62  		PendingChanges                 []string              `json:"pendingChanges,omitempty"`
    63  		RA                             string                `json:"ra"`
    64  		SignatureAlgorithm             string                `json:"signatureAlgorithm,omitempty"`
    65  		TechContact                    *Contact              `json:"techContact"`
    66  		ThirdParty                     *ThirdParty           `json:"thirdParty,omitempty"`
    67  		ValidationType                 string                `json:"validationType"`
    68  	}
    69  
    70  	// Contact contains contact information
    71  	Contact struct {
    72  		AddressLineOne   string `json:"addressLineOne,omitempty"`
    73  		AddressLineTwo   string `json:"addressLineTwo,omitempty"`
    74  		City             string `json:"city,omitempty"`
    75  		Country          string `json:"country,omitempty"`
    76  		Email            string `json:"email,omitempty"`
    77  		FirstName        string `json:"firstName,omitempty"`
    78  		LastName         string `json:"lastName,omitempty"`
    79  		OrganizationName string `json:"organizationName,omitempty"`
    80  		Phone            string `json:"phone,omitempty"`
    81  		PostalCode       string `json:"postalCode,omitempty"`
    82  		Region           string `json:"region,omitempty"`
    83  		Title            string `json:"title,omitempty"`
    84  	}
    85  
    86  	// CSR is a Certificate Signing Request object
    87  	CSR struct {
    88  		C    string   `json:"c,omitempty"`
    89  		CN   string   `json:"cn"`
    90  		L    string   `json:"l,omitempty"`
    91  		O    string   `json:"o,omitempty"`
    92  		OU   string   `json:"ou,omitempty"`
    93  		SANS []string `json:"sans,omitempty"`
    94  		ST   string   `json:"st,omitempty"`
    95  	}
    96  
    97  	// NetworkConfiguration contains settings that specify any network information and TLS Metadata you want CPS to use to push the completed certificate to the network
    98  	NetworkConfiguration struct {
    99  		ClientMutualAuthentication *ClientMutualAuthentication `json:"clientMutualAuthentication,omitempty"`
   100  		DisallowedTLSVersions      []string                    `json:"disallowedTlsVersions,omitempty"`
   101  		DNSNameSettings            *DNSNameSettings            `json:"dnsNameSettings,omitempty"`
   102  		Geography                  string                      `json:"geography,omitempty"`
   103  		MustHaveCiphers            string                      `json:"mustHaveCiphers,omitempty"`
   104  		OCSPStapling               OCSPStapling                `json:"ocspStapling,omitempty"`
   105  		PreferredCiphers           string                      `json:"preferredCiphers,omitempty"`
   106  		QuicEnabled                bool                        `json:"quicEnabled"`
   107  		SecureNetwork              string                      `json:"secureNetwork,omitempty"`
   108  		SNIOnly                    bool                        `json:"sniOnly"`
   109  	}
   110  
   111  	// ClientMutualAuthentication specifies the trust chain that is used to verify client certificates and some configuration options
   112  	ClientMutualAuthentication struct {
   113  		AuthenticationOptions *AuthenticationOptions `json:"authenticationOptions,omitempty"`
   114  		SetID                 string                 `json:"setId,omitempty"`
   115  	}
   116  
   117  	// AuthenticationOptions contain the configuration options for the selected trust chain
   118  	AuthenticationOptions struct {
   119  		OCSP               *OCSP `json:"ocsp,omitempty"`
   120  		SendCAListToClient *bool `json:"sendCaListToClient,omitempty"`
   121  	}
   122  
   123  	// OCSP specifies whether you want to enable ocsp stapling for client certificates
   124  	OCSP struct {
   125  		Enabled *bool `json:"enabled,omitempty"`
   126  	}
   127  
   128  	// DNSNameSettings contain DNS name setting in given network configuration
   129  	DNSNameSettings struct {
   130  		CloneDNSNames bool     `json:"cloneDnsNames"`
   131  		DNSNames      []string `json:"dnsNames,omitempty"`
   132  	}
   133  
   134  	// Org represents organization information
   135  	Org struct {
   136  		AddressLineOne string `json:"addressLineOne,omitempty"`
   137  		AddressLineTwo string `json:"addressLineTwo,omitempty"`
   138  		City           string `json:"city,omitempty"`
   139  		Country        string `json:"country,omitempty"`
   140  		Name           string `json:"name,omitempty"`
   141  		Phone          string `json:"phone,omitempty"`
   142  		PostalCode     string `json:"postalCode,omitempty"`
   143  		Region         string `json:"region,omitempty"`
   144  	}
   145  
   146  	// ThirdParty specifies that you want to use a third party certificate
   147  	ThirdParty struct {
   148  		ExcludeSANS bool `json:"excludeSans"`
   149  	}
   150  
   151  	// ListEnrollmentsRequest contains Contract ID of enrollments that are to be fetched with ListEnrollments
   152  	ListEnrollmentsRequest struct {
   153  		ContractID string
   154  	}
   155  
   156  	// GetEnrollmentRequest contains ID of an enrollment that is to be fetched with GetEnrollment
   157  	GetEnrollmentRequest struct {
   158  		EnrollmentID int
   159  	}
   160  
   161  	// CreateEnrollmentRequest contains request body and path parameters used to create an enrollment
   162  	CreateEnrollmentRequest struct {
   163  		Enrollment
   164  		ContractID       string
   165  		DeployNotAfter   string
   166  		DeployNotBefore  string
   167  		AllowDuplicateCN bool
   168  	}
   169  
   170  	// CreateEnrollmentResponse contains response body returned after successful enrollment creation
   171  	CreateEnrollmentResponse struct {
   172  		ID         int
   173  		Enrollment string   `json:"enrollment"`
   174  		Changes    []string `json:"changes"`
   175  	}
   176  
   177  	// UpdateEnrollmentRequest contains request body and path parameters used to update an enrollment
   178  	UpdateEnrollmentRequest struct {
   179  		Enrollment
   180  		EnrollmentID              int
   181  		AllowCancelPendingChanges *bool
   182  		AllowStagingBypass        *bool
   183  		DeployNotAfter            string
   184  		DeployNotBefore           string
   185  		ForceRenewal              *bool
   186  		RenewalDateCheckOverride  *bool
   187  	}
   188  
   189  	// UpdateEnrollmentResponse contains response body returned after successful enrollment update
   190  	UpdateEnrollmentResponse struct {
   191  		ID         int
   192  		Enrollment string   `json:"enrollment"`
   193  		Changes    []string `json:"changes"`
   194  	}
   195  
   196  	// RemoveEnrollmentRequest contains parameters necessary to send a RemoveEnrollment request
   197  	RemoveEnrollmentRequest struct {
   198  		EnrollmentID              int
   199  		AllowCancelPendingChanges *bool
   200  		DeployNotAfter            string
   201  		DeployNotBefore           string
   202  	}
   203  
   204  	// RemoveEnrollmentResponse contains response body returned after successful enrollment deletion
   205  	RemoveEnrollmentResponse struct {
   206  		Enrollment string   `json:"enrollment"`
   207  		Changes    []string `json:"changes"`
   208  	}
   209  
   210  	// OCSPStapling is used to enable OCSP stapling for an enrollment
   211  	OCSPStapling string
   212  )
   213  
   214  const (
   215  	// OCSPStaplingOn parameter value
   216  	OCSPStaplingOn OCSPStapling = "on"
   217  	// OCSPStaplingOff parameter value
   218  	OCSPStaplingOff OCSPStapling = "off"
   219  	// OCSPStaplingNotSet parameter value
   220  	OCSPStaplingNotSet OCSPStapling = "not-set"
   221  )
   222  
   223  // Validate performs validation on Enrollment
   224  func (e Enrollment) Validate() error {
   225  	return validation.Errors{
   226  		"adminContact":         validation.Validate(e.AdminContact, validation.Required),
   227  		"certificateType":      validation.Validate(e.CertificateType, validation.Required),
   228  		"csr":                  validation.Validate(e.CSR, validation.Required),
   229  		"networkConfiguration": validation.Validate(e.NetworkConfiguration, validation.Required),
   230  		"org":                  validation.Validate(e.Org, validation.Required),
   231  		"ra":                   validation.Validate(e.RA, validation.Required),
   232  		"techContact":          validation.Validate(e.TechContact, validation.Required),
   233  		"validationType":       validation.Validate(e.ValidationType, validation.Required),
   234  		"thirdParty":           validation.Validate(e.ThirdParty),
   235  	}.Filter()
   236  }
   237  
   238  // Validate performs validation on Enrollment
   239  func (c CSR) Validate() error {
   240  	return validation.Errors{
   241  		"cn": validation.Validate(c.CN, validation.Required),
   242  	}.Filter()
   243  }
   244  
   245  // Validate performs validation on ThirdParty
   246  func (t ThirdParty) Validate() error {
   247  	return validation.Errors{
   248  		"excludeSans": validation.Validate(t.ExcludeSANS, validation.Required),
   249  	}.Filter()
   250  }
   251  
   252  // Validate performs validation on NetworkConfiguration
   253  func (n NetworkConfiguration) Validate() error {
   254  	return validation.Errors{
   255  		"ocspStapling": validation.Validate(n.OCSPStapling, validation.In(OCSPStaplingOn, OCSPStaplingOff, OCSPStaplingNotSet)),
   256  	}.Filter()
   257  }
   258  
   259  // Validate performs validation on ListEnrollmentRequest
   260  func (e ListEnrollmentsRequest) Validate() error {
   261  	return validation.Errors{
   262  		"contractId": validation.Validate(e.ContractID, validation.Required),
   263  	}.Filter()
   264  }
   265  
   266  // Validate performs validation on GetEnrollmentRequest
   267  func (e GetEnrollmentRequest) Validate() error {
   268  	return validation.Errors{
   269  		"enrollmentId": validation.Validate(e.EnrollmentID, validation.Required),
   270  	}.Filter()
   271  }
   272  
   273  // Validate performs validation on CreateEnrollmentRequest
   274  func (e CreateEnrollmentRequest) Validate() error {
   275  	return validation.Errors{
   276  		"enrollment": validation.Validate(e.Enrollment, validation.Required),
   277  		"contractId": validation.Validate(e.ContractID, validation.Required),
   278  	}.Filter()
   279  }
   280  
   281  // Validate performs validation on UpdateEnrollmentRequest
   282  func (e UpdateEnrollmentRequest) Validate() error {
   283  	return validation.Errors{
   284  		"enrollment":   validation.Validate(e.Enrollment, validation.Required),
   285  		"enrollmentId": validation.Validate(e.EnrollmentID, validation.Required),
   286  	}.Filter()
   287  }
   288  
   289  // Validate performs validation on RemoveEnrollmentRequest
   290  func (e RemoveEnrollmentRequest) Validate() error {
   291  	return validation.Errors{
   292  		"enrollmentId": validation.Validate(e.EnrollmentID, validation.Required),
   293  	}.Filter()
   294  }
   295  
   296  var (
   297  	// ErrListEnrollments is returned when ListEnrollments fails
   298  	ErrListEnrollments = errors.New("fetching enrollments")
   299  	// ErrGetEnrollment is returned when GetEnrollment fails
   300  	ErrGetEnrollment = errors.New("fetching enrollment")
   301  	// ErrCreateEnrollment is returned when CreateEnrollment fails
   302  	ErrCreateEnrollment = errors.New("create enrollment")
   303  	// ErrUpdateEnrollment is returned when UpdateEnrollment fails
   304  	ErrUpdateEnrollment = errors.New("update enrollment")
   305  	// ErrRemoveEnrollment is returned when RemoveEnrollment fails
   306  	ErrRemoveEnrollment = errors.New("remove enrollment")
   307  )
   308  
   309  func (c *cps) ListEnrollments(ctx context.Context, params ListEnrollmentsRequest) (*ListEnrollmentsResponse, error) {
   310  	if err := params.Validate(); err != nil {
   311  		return nil, fmt.Errorf("%s: %w: %s", ErrListEnrollments, ErrStructValidation, err)
   312  	}
   313  
   314  	logger := c.Log(ctx)
   315  	logger.Debug("ListEnrollments")
   316  
   317  	uri := fmt.Sprintf("/cps/v2/enrollments?contractId=%s", params.ContractID)
   318  
   319  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
   320  	if err != nil {
   321  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrListEnrollments, err)
   322  	}
   323  	req.Header.Set("Accept", "application/vnd.akamai.cps.enrollments.v9+json")
   324  
   325  	var result ListEnrollmentsResponse
   326  
   327  	resp, err := c.Exec(req, &result)
   328  	if err != nil {
   329  		return nil, fmt.Errorf("%w: request failed: %s", ErrListEnrollments, err)
   330  	}
   331  
   332  	if resp.StatusCode != http.StatusOK {
   333  		return nil, fmt.Errorf("%s: %w", ErrListEnrollments, c.Error(resp))
   334  	}
   335  
   336  	return &result, nil
   337  }
   338  
   339  func (c *cps) GetEnrollment(ctx context.Context, params GetEnrollmentRequest) (*Enrollment, error) {
   340  	if err := params.Validate(); err != nil {
   341  		return nil, fmt.Errorf("%s: %w: %s", ErrGetEnrollment, ErrStructValidation, err)
   342  	}
   343  
   344  	logger := c.Log(ctx)
   345  	logger.Debug("GetEnrollment")
   346  
   347  	uri := fmt.Sprintf("/cps/v2/enrollments/%d", params.EnrollmentID)
   348  
   349  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
   350  	if err != nil {
   351  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetEnrollment, err)
   352  	}
   353  	req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment.v9+json")
   354  
   355  	var result Enrollment
   356  
   357  	resp, err := c.Exec(req, &result)
   358  	if err != nil {
   359  		return nil, fmt.Errorf("%w: request failed: %s", ErrGetEnrollment, err)
   360  	}
   361  
   362  	if resp.StatusCode != http.StatusOK {
   363  		return nil, fmt.Errorf("%s: %w", ErrGetEnrollment, c.Error(resp))
   364  	}
   365  
   366  	return &result, nil
   367  }
   368  
   369  func (c *cps) CreateEnrollment(ctx context.Context, params CreateEnrollmentRequest) (*CreateEnrollmentResponse, error) {
   370  	if err := params.Validate(); err != nil {
   371  		return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrStructValidation, err)
   372  	}
   373  
   374  	logger := c.Log(ctx)
   375  	logger.Debug("CreateEnrollment")
   376  
   377  	uri, err := url.Parse(fmt.Sprintf("/cps/v2/enrollments?contractId=%s", params.ContractID))
   378  	if err != nil {
   379  		return nil, fmt.Errorf("%w: parsing URL: %s", ErrCreateEnrollment, err)
   380  	}
   381  	query := uri.Query()
   382  	if params.DeployNotAfter != "" {
   383  		query.Add("deploy-not-after", params.DeployNotAfter)
   384  	}
   385  	if params.DeployNotBefore != "" {
   386  		query.Add("deploy-not-before", params.DeployNotBefore)
   387  	}
   388  	if params.AllowDuplicateCN {
   389  		query.Add("allow-duplicate-cn", "true")
   390  	}
   391  	uri.RawQuery = query.Encode()
   392  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil)
   393  	if err != nil {
   394  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateEnrollment, err)
   395  	}
   396  	req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment-status.v1+json")
   397  	req.Header.Set("Content-Type", "application/vnd.akamai.cps.enrollment.v9+json")
   398  
   399  	var result CreateEnrollmentResponse
   400  
   401  	resp, err := c.Exec(req, &result, params.Enrollment)
   402  	if err != nil {
   403  		return nil, fmt.Errorf("%w: request failed: %s", ErrCreateEnrollment, err)
   404  	}
   405  
   406  	if resp.StatusCode != http.StatusAccepted {
   407  		return nil, fmt.Errorf("%s: %w", ErrCreateEnrollment, c.Error(resp))
   408  	}
   409  	id, err := GetIDFromLocation(result.Enrollment)
   410  	if err != nil {
   411  		return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrInvalidLocation, err)
   412  	}
   413  	result.ID = id
   414  
   415  	return &result, nil
   416  }
   417  
   418  func (c *cps) UpdateEnrollment(ctx context.Context, params UpdateEnrollmentRequest) (*UpdateEnrollmentResponse, error) {
   419  	if err := params.Validate(); err != nil {
   420  		return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrStructValidation, err)
   421  	}
   422  
   423  	logger := c.Log(ctx)
   424  	logger.Debug("UpdateEnrollment")
   425  
   426  	uri, err := url.Parse(fmt.Sprintf("/cps/v2/enrollments/%d", params.EnrollmentID))
   427  	if err != nil {
   428  		return nil, fmt.Errorf("%w: parsing URL: %s", ErrUpdateEnrollment, err)
   429  	}
   430  	query := uri.Query()
   431  	if params.AllowCancelPendingChanges != nil {
   432  		query.Add("allow-cancel-pending-changes", strconv.FormatBool(*params.AllowCancelPendingChanges))
   433  	}
   434  	if params.AllowStagingBypass != nil {
   435  		query.Add("allow-staging-bypass", strconv.FormatBool(*params.AllowStagingBypass))
   436  	}
   437  	if params.DeployNotAfter != "" {
   438  		query.Add("deploy-not-after", params.DeployNotAfter)
   439  	}
   440  	if params.DeployNotBefore != "" {
   441  		query.Add("deploy-not-before", params.DeployNotBefore)
   442  	}
   443  	if params.ForceRenewal != nil {
   444  		query.Add("force-renewal", strconv.FormatBool(*params.ForceRenewal))
   445  	}
   446  	if params.RenewalDateCheckOverride != nil {
   447  		query.Add("renewal-date-check-override", strconv.FormatBool(*params.RenewalDateCheckOverride))
   448  	}
   449  
   450  	uri.RawQuery = query.Encode()
   451  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil)
   452  	if err != nil {
   453  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateEnrollment, err)
   454  	}
   455  	req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment-status.v1+json")
   456  	req.Header.Set("Content-Type", "application/vnd.akamai.cps.enrollment.v9+json")
   457  
   458  	var result UpdateEnrollmentResponse
   459  
   460  	resp, err := c.Exec(req, &result, params.Enrollment)
   461  	if err != nil {
   462  		return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateEnrollment, err)
   463  	}
   464  
   465  	if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK {
   466  		return nil, fmt.Errorf("%s: %w", ErrUpdateEnrollment, c.Error(resp))
   467  	}
   468  	id, err := GetIDFromLocation(result.Enrollment)
   469  	if err != nil {
   470  		return nil, fmt.Errorf("%s: %w: %s", ErrCreateEnrollment, ErrInvalidLocation, err)
   471  	}
   472  	result.ID = id
   473  
   474  	return &result, nil
   475  }
   476  
   477  func (c *cps) RemoveEnrollment(ctx context.Context, params RemoveEnrollmentRequest) (*RemoveEnrollmentResponse, error) {
   478  	if err := params.Validate(); err != nil {
   479  		return nil, fmt.Errorf("%s: %w: %s", ErrRemoveEnrollment, ErrStructValidation, err)
   480  	}
   481  
   482  	logger := c.Log(ctx)
   483  	logger.Debug("RemoveEnrollment")
   484  
   485  	uri, err := url.Parse(fmt.Sprintf("/cps/v2/enrollments/%d", params.EnrollmentID))
   486  	if err != nil {
   487  		return nil, fmt.Errorf("%w: parsing URL: %s", ErrRemoveEnrollment, err)
   488  	}
   489  	query := uri.Query()
   490  	if params.AllowCancelPendingChanges != nil {
   491  		query.Add("allow-cancel-pending-changes", strconv.FormatBool(*params.AllowCancelPendingChanges))
   492  	}
   493  	if params.DeployNotAfter != "" {
   494  		query.Add("deploy-not-after", params.DeployNotAfter)
   495  	}
   496  	if params.DeployNotBefore != "" {
   497  		query.Add("deploy-not-before", params.DeployNotBefore)
   498  	}
   499  
   500  	uri.RawQuery = query.Encode()
   501  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil)
   502  	if err != nil {
   503  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrRemoveEnrollment, err)
   504  	}
   505  	req.Header.Set("Accept", "application/vnd.akamai.cps.enrollment-status.v1+json")
   506  
   507  	var result RemoveEnrollmentResponse
   508  
   509  	resp, err := c.Exec(req, &result)
   510  	if err != nil {
   511  		return nil, fmt.Errorf("%w: request failed: %s", ErrRemoveEnrollment, err)
   512  	}
   513  
   514  	if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK {
   515  		return nil, fmt.Errorf("%s: %w", ErrRemoveEnrollment, c.Error(resp))
   516  	}
   517  
   518  	return &result, nil
   519  }