30939 Migrating CAs from App Config JSON to their own table (#31739)

Migrates CAs out of the App Config JSON and into their own table
including their secrets. Does not implement CRUD endpoints or update
clients of existing app config to use them yet.

I think there are good arguments both for keeping secrets in separate
tables and keeping them in this table but on balance I felt it was best
to move everything CA to this new table

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually

## Database migrations

- [x] Checked table schema to confirm autoupdate
- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
This commit is contained in:
Jordan Montgomery
2025-08-11 11:37:55 -04:00
committed by GitHub
parent bc3656781c
commit da55b5f75a
5 changed files with 506 additions and 16 deletions
@@ -0,0 +1,236 @@
package tables
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/reflectx"
)
func init() {
MigrationClient.AddMigration(Up_20250811084533, Down_20250811084533)
}
// dbCertificateAuthority embeds fleet.CertificateAuthority and adds encrypted representation of sensitive
// fields for handling DB operations
type dbCertificateAuthority struct {
fleet.CertificateAuthority
// Digicert
APITokenEncrypted []byte `db:"api_token_encrypted"`
CertificateUserPrincipalNamesRaw []byte `db:"certificate_user_principal_names"`
// NDES SCEP Proxy
PasswordEncrypted []byte `db:"password_encrypted"`
// Custom SCEP Proxy
ChallengeEncrypted []byte `db:"challenge_encrypted"`
// Hydrant
ClientSecretEncrypted []byte `db:"client_secret_encrypted"`
}
func Up_20250811084533(tx *sql.Tx) error {
txx := sqlx.Tx{Tx: tx, Mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper)}
stmt := `
CREATE TABLE IF NOT EXISTS certificate_authorities (
id INT AUTO_INCREMENT PRIMARY KEY,
type ENUM('digicert', 'ndes_scep_proxy', 'custom_scep_proxy', 'hydrant') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
-- Common fields
name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, -- Used by digicert and custom_scep_proxy
url TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, -- Used by all types
-- DigiCert fields
api_token_encrypted BLOB, -- previously stored in ca_config_assets
profile_id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
certificate_common_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
certificate_user_principal_names JSON, -- Array of UPNs
certificate_seat_id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
-- NDES fields
admin_url TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
username VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
password_encrypted BLOB, -- previously stored in mdm_config_assets
-- Custom SCEP
challenge_encrypted BLOB, -- previously stored in ca_config_assets
-- Hydrant fields
client_id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
client_secret_encrypted BLOB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`
// Create the table then iterate through app_config_json to populate it
_, err := txx.Exec(stmt)
if err != nil {
return fmt.Errorf("failed to create certificate_authorities table: %w", err)
}
// Create unique indexes on name and type(i.e. can't have more than 1 CA of a given type with a
// given name)
_, err = txx.Exec(`CREATE UNIQUE INDEX idx_ca_type_name ON certificate_authorities (type, name)`)
if err != nil {
return fmt.Errorf("failed to create unique index on certificate_authorities: %w", err)
}
// Populate the table with existing data from app_config_json
// if appConfigJSON.integrations.ndes_scep_proxy ...
// if appConfigJSON.integrations.custom_scep_proxy
// if appConfigJSON.integrations.hydrant ...
// if appConfigJSON.integrations.digicert ...
appConfigSelect := `SELECT json_value FROM app_config_json LIMIT 1`
var appConfigJSON fleet.AppConfig
jsonBytes := []byte{}
if err := txx.Get(&jsonBytes, appConfigSelect); err != nil {
return fmt.Errorf("failed to get app_config_json: %w", err)
}
if err := json.Unmarshal(jsonBytes, &appConfigJSON); err != nil {
return fmt.Errorf("failed to unmarshal app_config_json: %w", err)
}
configAssets := []fleet.CAConfigAsset{}
assetSelectStmt := `
SELECT
name, type, value
FROM
ca_config_assets
`
if err := txx.Select(&configAssets, assetSelectStmt); err != nil {
return fmt.Errorf("failed to get ca_config_assets: %w", err)
}
getCAConfigAsset := func(name string, assetType fleet.CAConfigAssetType) *fleet.CAConfigAsset {
for _, asset := range configAssets {
if asset.Name == name && asset.Type == assetType {
return &asset
}
}
return nil
}
casToInsert := []dbCertificateAuthority{}
if appConfigJSON.Integrations.CustomSCEPProxy.Valid && len(appConfigJSON.Integrations.CustomSCEPProxy.Value) > 0 {
for _, customSCEPProxyCA := range appConfigJSON.Integrations.CustomSCEPProxy.Value {
customSCEPChallenge := getCAConfigAsset(customSCEPProxyCA.Name, fleet.CAConfigCustomSCEPProxy)
if customSCEPChallenge == nil || len(customSCEPChallenge.Value) == 0 {
return errors.New("Custom SCEP Proxy challenge not found in ca_config_assets")
}
casToInsert = append(casToInsert, dbCertificateAuthority{
CertificateAuthority: fleet.CertificateAuthority{
Type: string(fleet.CATypeCustomSCEPProxy),
Name: customSCEPProxyCA.Name,
URL: customSCEPProxyCA.URL,
},
ChallengeEncrypted: customSCEPChallenge.Value,
})
}
}
if appConfigJSON.Integrations.DigiCert.Valid && len(appConfigJSON.Integrations.DigiCert.Value) > 0 {
for _, digicertCA := range appConfigJSON.Integrations.DigiCert.Value {
digicertAPIToken := getCAConfigAsset(digicertCA.Name, fleet.CAConfigDigiCert)
if digicertAPIToken == nil || len(digicertAPIToken.Value) == 0 {
return errors.New("DigiCert API token not found in ca_config_assets")
}
casToInsert = append(casToInsert, dbCertificateAuthority{
CertificateAuthority: fleet.CertificateAuthority{
Type: string(fleet.CATypeDigiCert),
Name: digicertCA.Name,
URL: digicertCA.URL,
ProfileID: &digicertCA.ProfileID,
CertificateCommonName: &digicertCA.CertificateCommonName,
CertificateUserPrincipalNames: digicertCA.CertificateUserPrincipalNames,
CertificateSeatID: &digicertCA.CertificateSeatID,
},
APITokenEncrypted: digicertAPIToken.Value,
})
}
}
if appConfigJSON.Integrations.NDESSCEPProxy.Valid {
ndesCAPassword := []byte{}
err = txx.Get(&ndesCAPassword, `SELECT value FROM mdm_config_assets WHERE name = ?`, fleet.MDMAssetNDESPassword)
if err != nil {
return fmt.Errorf("failed to get NDES SCEP Proxy password: %w", err)
}
if len(ndesCAPassword) == 0 {
return errors.New("NDES SCEP Proxy password not found in mdm_config_assets")
}
// Insert NDES SCEP Proxy data
ndesCA := appConfigJSON.Integrations.NDESSCEPProxy.Value
dbNDESCA := dbCertificateAuthority{
CertificateAuthority: fleet.CertificateAuthority{
Type: string(fleet.CATypeNDESSCEPProxy),
Name: "DEFAULT_NDES_CA",
URL: ndesCA.URL,
AdminURL: &ndesCA.AdminURL,
Username: &ndesCA.Username,
},
PasswordEncrypted: ndesCAPassword,
}
casToInsert = append(casToInsert, dbNDESCA)
}
for _, ca := range casToInsert {
insertStmt := `
INSERT INTO certificate_authorities (
type,
name,
url,
api_token_encrypted,
profile_id,
certificate_common_name,
certificate_user_principal_names,
certificate_seat_id,
admin_url,
username,
password_encrypted,
challenge_encrypted,
client_id,
client_secret_encrypted
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
var upns []byte
if ca.CertificateUserPrincipalNames != nil {
upns, err = json.Marshal(ca.CertificateUserPrincipalNames)
if err != nil {
return fmt.Errorf("failed to marshal certificate user principal names for %s: %w", ca.Name, err)
}
}
args := []interface{}{
ca.Type,
ca.Name,
ca.URL,
ca.APITokenEncrypted,
ca.ProfileID,
ca.CertificateCommonName,
upns,
ca.CertificateSeatID,
ca.AdminURL,
ca.Username,
ca.PasswordEncrypted,
ca.ChallengeEncrypted,
ca.ClientID,
ca.ClientSecretEncrypted,
}
_, err = txx.Exec(insertStmt, args...)
if err != nil {
return fmt.Errorf("failed to insert certificate authority %s: %w", ca.Name, err)
}
}
return nil
}
func Down_20250811084533(tx *sql.Tx) error {
return nil
}
@@ -0,0 +1,185 @@
package tables
import (
"crypto/md5" // nolint:gosec // used only to hash for efficient comparisons
"encoding/hex"
"encoding/json"
"strings"
"testing"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func md5ChecksumBytes(b []byte) string {
rawChecksum := md5.Sum(b) //nolint:gosec
return strings.ToUpper(hex.EncodeToString(rawChecksum[:]))
}
func TestUp_20250811084533(t *testing.T) {
db := applyUpToPrev(t)
var appConfigJSON fleet.AppConfig
const (
digicertCA1Name = "DigiCert_CA_1"
digicertCA2Name = "DigiCert_CA_2"
customSCEPCA1Name = "Custom_SCEP_Proxy_CA_1"
customSCEPCA2Name = "Custom_SCEP_Proxy_CA_2"
)
// various strings encrypted with key my-secret-key-123456781234567890 just to verify the
// migration works with opaque(to it) binary data
customSCEPCA1EncryptedChallenge := []byte{0xe0, 0xd1, 0xba, 0x48, 0x20, 0x31, 0xff, 0x52, 0x11, 0x2c, 0x62, 0x0d, 0x8e, 0xc3, 0xb7, 0x88, 0x13, 0x5d, 0x37, 0x04, 0x10, 0xf4, 0xda, 0xa4, 0x8e, 0x18, 0xf7, 0x95, 0x8f, 0x5d, 0x1c, 0xc4, 0xb7, 0x45, 0xeb, 0xa9, 0xbf, 0x7d}
customSCEPCA2EncryptedChallenge := []byte{0x8b, 0x92, 0x95, 0xbf, 0xda, 0xc1, 0x71, 0x11, 0x64, 0xdc, 0xd0, 0xa8, 0x1c, 0x91, 0x26, 0xec, 0x15, 0xd5, 0x21, 0xca, 0x5e, 0x62, 0x71, 0x01, 0x5e, 0xb1, 0xb1, 0xbf, 0x9f, 0xe6, 0x36, 0x79, 0xa2, 0xee, 0x32, 0x9d, 0x64, 0x7c}
ndesEncryptedPassword := []byte{0xaa, 0x20, 0x6e, 0xb0, 0xb5, 0x10, 0x52, 0x6d, 0xe2, 0x78, 0x14, 0xbe, 0xc5, 0xe0, 0x8a, 0x04, 0xa6, 0xfd, 0x8b, 0x17, 0xd8, 0x15, 0x71, 0x3c, 0x72, 0xa2, 0x76, 0x5f, 0xba, 0x5d, 0x10, 0x41, 0x21, 0x56, 0xe4, 0x1d, 0xa0, 0x90, 0x0d, 0x9e, 0xe1}
digicertCA1EncryptedPassword := []byte{0xd5, 0xf1, 0x50, 0x6f, 0x59, 0xb4, 0xfe, 0xa4, 0x3a, 0xc4, 0x24, 0xc8, 0xfa, 0xfd, 0x43, 0xc0, 0xec, 0x2d, 0x10, 0xb1, 0x2a, 0x1e, 0xa8, 0x1e, 0x62, 0x2f, 0x04, 0xeb, 0xb5, 0x55, 0xea, 0x92, 0xfe, 0xb2, 0x9b, 0x6b, 0xc0, 0x98, 0x70, 0x2c, 0x33, 0xf6, 0x01, 0x0f, 0x13, 0x06, 0xef, 0xee, 0x81, 0xb9}
digicertCA2EncryptedPassword := []byte{0x24, 0x46, 0x38, 0xa5, 0x75, 0xe4, 0x34, 0x2a, 0x99, 0x5d, 0x52, 0xc9, 0xb1, 0x05, 0x05, 0xa1, 0xdf, 0x62, 0xe2, 0xf1, 0x01, 0x92, 0x0b, 0xcd, 0xd4, 0x49, 0x83, 0x2e, 0xff, 0xd6, 0x23, 0x5c, 0x75, 0x57, 0x57, 0x18, 0x42, 0x3c, 0x81, 0x78, 0xf2, 0x86, 0x59, 0x42, 0x11, 0xb5, 0x82, 0x23, 0x3a, 0x91}
ndesCA := fleet.NDESSCEPProxyIntegration{
URL: "https://ndes.example.com",
AdminURL: "https://admin.ndes.example.com",
Username: "admin",
Password: fleet.MaskedPassword,
}
digicertCAs := []fleet.DigiCertIntegration{{
URL: "https://api.digicert.com",
ProfileID: "profile-id-1",
Name: digicertCA1Name,
APIToken: fleet.MaskedPassword,
CertificateCommonName: "Common-Name1: $FLEET_VAR_HOST_HARDWARE_SERIAL",
CertificateUserPrincipalNames: []string{"UPN1: $FLEET_VAR_HOST_HARDWARE_SERIAL"},
CertificateSeatID: "Seat-ID1: $FLEET_VAR_HOST_HARDWARE_SERIAL",
}, {
URL: "https://api.digicert.com",
ProfileID: "profile-id-2",
Name: digicertCA2Name,
APIToken: fleet.MaskedPassword,
CertificateCommonName: "Common-Name2: $FLEET_VAR_HOST_HARDWARE_SERIAL",
CertificateUserPrincipalNames: []string{"UPN2: $FLEET_VAR_HOST_HARDWARE_SERIAL"},
CertificateSeatID: "Seat-ID2: $FLEET_VAR_HOST_HARDWARE_SERIAL",
}}
customSCEPProxyCAs := []fleet.CustomSCEPProxyIntegration{{
URL: "https://custom-scep-1.example.com",
Name: customSCEPCA1Name,
Challenge: fleet.MaskedPassword,
}, {
URL: "https://custom-scep-2.example.com",
Name: customSCEPCA2Name,
Challenge: fleet.MaskedPassword,
}}
appConfigJSON.Integrations.CustomSCEPProxy.Value = customSCEPProxyCAs
appConfigJSON.Integrations.CustomSCEPProxy.Set = true
appConfigJSON.Integrations.CustomSCEPProxy.Valid = true
appConfigJSON.Integrations.NDESSCEPProxy.Value = ndesCA
appConfigJSON.Integrations.NDESSCEPProxy.Set = true
appConfigJSON.Integrations.NDESSCEPProxy.Valid = true
appConfigJSON.Integrations.DigiCert.Value = digicertCAs
appConfigJSON.Integrations.DigiCert.Set = true
appConfigJSON.Integrations.DigiCert.Valid = true
jsonBytes, err := json.Marshal(&appConfigJSON)
if err != nil {
t.Fatalf("failed to marshal appConfigJSON: %v", err)
}
insertNDESPasswordStmt := `INSERT INTO mdm_config_assets (name, value, md5_checksum) VALUES (?, ?, UNHEX(?))` // nolint:gosec // just test data, not hardcoded credentials
_, err = db.Exec(insertNDESPasswordStmt, fleet.MDMAssetNDESPassword, ndesEncryptedPassword, md5ChecksumBytes(ndesEncryptedPassword))
require.NoError(t, err, "failed to insert NDES SCEP Proxy password")
insertCAAssetsStmt := `INSERT INTO ca_config_assets (name, value, type) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?)`
_, err = db.Exec(insertCAAssetsStmt,
digicertCA1Name, digicertCA1EncryptedPassword, fleet.CAConfigDigiCert,
digicertCA2Name, digicertCA2EncryptedPassword, fleet.CAConfigDigiCert,
customSCEPCA1Name, customSCEPCA1EncryptedChallenge, fleet.CAConfigCustomSCEPProxy,
customSCEPCA2Name, customSCEPCA2EncryptedChallenge, fleet.CAConfigCustomSCEPProxy,
)
require.NoError(t, err, "failed to insert ca_config_assets")
_, err = db.Exec(
`INSERT INTO app_config_json(json_value) VALUES(?) ON DUPLICATE KEY UPDATE json_value = VALUES(json_value)`,
jsonBytes,
)
if err != nil {
require.NoError(t, err, "failed to insert app_config_json")
}
// Apply current migration.
applyNext(t, db)
type dbCertificateAuthority struct {
fleet.CertificateAuthority
// Digicert
APITokenEncrypted []byte `db:"api_token_encrypted"`
CertificateUserPrincipalNamesRaw []byte `db:"certificate_user_principal_names"`
// NDES SCEP Proxy
PasswordEncrypted []byte `db:"password_encrypted"`
// Custom SCEP Proxy
ChallengeEncrypted []byte `db:"challenge_encrypted"`
// Hydrant
ClientSecretEncrypted []byte `db:"client_secret_encrypted"`
}
cas := []dbCertificateAuthority{}
stmt := `SELECT type, name, url, api_token_encrypted, profile_id, certificate_common_name, certificate_user_principal_names, certificate_seat_id, admin_url, username, password_encrypted, challenge_encrypted, client_id, client_secret_encrypted, created_at, updated_at
FROM certificate_authorities`
err = db.Select(&cas, stmt)
casFound := []string{}
require.NoError(t, err, "failed to select certificate authorities")
require.Len(t, cas, 5, "expected 5 certificate authorities")
for _, ca := range cas {
if ca.CertificateUserPrincipalNamesRaw != nil {
err = json.Unmarshal(ca.CertificateUserPrincipalNamesRaw, &ca.CertificateUserPrincipalNames)
require.NoErrorf(t, err, "failed to unmarshal certificate user principal names for %s", ca.Name)
}
casFound = append(casFound, ca.Name)
// No Hydrant CAs in this test so these should be nil
assert.Nil(t, ca.ClientID)
assert.Nil(t, ca.ClientSecret)
switch ca.Type {
case "digicert":
assert.Contains(t, []string{digicertCA1Name, digicertCA2Name}, ca.Name, "unexpected DigiCert CA name")
expectedCA := digicertCAs[0]
expectedAPIToken := digicertCA1EncryptedPassword
if ca.Name == digicertCA2Name {
expectedCA = digicertCAs[1]
expectedAPIToken = digicertCA2EncryptedPassword
}
assert.Equal(t, expectedCA.URL, ca.URL)
assert.Equal(t, expectedAPIToken, ca.APITokenEncrypted)
require.NotNil(t, ca.ProfileID)
assert.Equal(t, expectedCA.ProfileID, *ca.ProfileID)
require.NotNil(t, ca.CertificateCommonName)
assert.Equal(t, expectedCA.CertificateCommonName, *ca.CertificateCommonName)
assert.Equal(t, expectedCA.CertificateUserPrincipalNames, ca.CertificateUserPrincipalNames)
require.NotNil(t, ca.CertificateSeatID)
assert.Equal(t, expectedCA.CertificateSeatID, *ca.CertificateSeatID)
case "custom_scep_proxy":
require.Contains(t, []string{customSCEPCA1Name, customSCEPCA2Name}, ca.Name, "unexpected Custom SCEP Proxy CA name")
expectedCA := customSCEPProxyCAs[0]
expectedChallenge := customSCEPCA1EncryptedChallenge
if ca.Name == customSCEPCA2Name {
expectedCA = customSCEPProxyCAs[1]
expectedChallenge = customSCEPCA2EncryptedChallenge
}
assert.Equal(t, expectedCA.URL, ca.URL)
assert.Equal(t, expectedChallenge, ca.ChallengeEncrypted)
assert.Nil(t, ca.CertificateUserPrincipalNames)
case "ndes_scep_proxy":
assert.Equal(t, "DEFAULT_NDES_CA", ca.Name)
require.NotNil(t, ca.AdminURL)
assert.Equal(t, ndesCA.AdminURL, *ca.AdminURL)
assert.Equal(t, ndesCA.URL, ca.URL)
require.NotNil(t, ca.Username)
assert.Equal(t, ndesCA.Username, *ca.Username)
assert.Equal(t, ndesEncryptedPassword, ca.PasswordEncrypted)
assert.Nil(t, ca.CertificateUserPrincipalNames)
default:
require.Failf(t, "unexpected certificate authority type", "type: %s, name: %s", ca.Type, ca.Name)
}
}
require.ElementsMatch(t, []string{digicertCA1Name, digicertCA2Name, customSCEPCA1Name, customSCEPCA2Name, "DEFAULT_NDES_CA"}, casFound)
}
File diff suppressed because one or more lines are too long
+59
View File
@@ -0,0 +1,59 @@
package fleet
import (
"time"
)
type CAConfigAssetType string
const (
CAConfigNDES CAConfigAssetType = "ndes"
CAConfigDigiCert CAConfigAssetType = "digicert"
CAConfigCustomSCEPProxy CAConfigAssetType = "custom_scep_proxy"
)
type CAConfigAsset struct {
Name string `db:"name"`
Value []byte `db:"value"`
Type CAConfigAssetType `db:"type"`
}
type CAType string
const (
CATypeNDESSCEPProxy CAType = "ndes_scep_proxy"
CATypeDigiCert CAType = "digicert"
CATypeCustomSCEPProxy CAType = "custom_scep_proxy"
CATypeHydrant CAType = "hydrant"
)
type CertificateAuthority struct {
ID uint `json:"id" db:"id"`
Type string `json:"type" db:"type"`
// common
Name string `json:"name" db:"name"`
URL string `json:"url" db:"url"`
// Digicert
APIToken *string `json:"api_token,omitempty" db:"-"`
ProfileID *string `json:"profile_id,omitempty" db:"profile_id"`
CertificateCommonName *string `json:"certificate_common_name,omitempty" db:"certificate_common_name"`
CertificateUserPrincipalNames []string `json:"certificate_user_principal_names,omitempty" db:"-"`
CertificateSeatID *string `json:"certificate_seat_id,omitempty" db:"certificate_seat_id"`
// NDES SCEP Proxy
AdminURL *string `json:"admin_url,omitempty" db:"admin_url"`
Username *string `json:"username,omitempty" db:"username"`
Password *string `json:"password,omitempty" db:"-"`
// Custom SCEP Proxy
Challenge *string `json:"challenge,omitempty" db:"-"`
// Hydrant
ClientID *string `json:"client_id,omitempty" db:"client_id"`
ClientSecret *string `json:"client_secret,omitempty" db:"-"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
-14
View File
@@ -805,20 +805,6 @@ func (m MDMConfigAsset) Copy() MDMConfigAsset {
return clone
}
type CAConfigAssetType string
const (
CAConfigNDES CAConfigAssetType = "ndes"
CAConfigDigiCert CAConfigAssetType = "digicert"
CAConfigCustomSCEPProxy CAConfigAssetType = "custom_scep_proxy"
)
type CAConfigAsset struct {
Name string `db:"name"`
Value []byte `db:"value"`
Type CAConfigAssetType `db:"type"`
}
// MDMPlatform returns "darwin" or "windows" as MDM platforms
// derived from a host's platform (hosts.platform field).
//