mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 08:28:52 +00:00
234 lines
6.7 KiB
Go
234 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/WatchBeam/clock"
|
|
"github.com/fleetdm/fleet/v4/server/config"
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
)
|
|
|
|
const (
|
|
testUsername = "fleet"
|
|
testPassword = "insecure"
|
|
testAddress = "localhost:3306"
|
|
testDatabase = "fleet"
|
|
)
|
|
|
|
var (
|
|
exportCmd = flag.NewFlagSet("export", flag.ExitOnError)
|
|
importCmd = flag.NewFlagSet("import", flag.ExitOnError)
|
|
flagKey string
|
|
flagDir string
|
|
flagDBUser string
|
|
flagDBPass string
|
|
flagDBAddress string
|
|
flagDBName string
|
|
flagImportName string
|
|
flagImportValue string
|
|
flagExportName string
|
|
|
|
validNames = map[fleet.MDMAssetName]struct{}{
|
|
fleet.MDMAssetABMCert: {},
|
|
fleet.MDMAssetABMTokenDeprecated: {},
|
|
fleet.MDMAssetABMKey: {},
|
|
fleet.MDMAssetAPNSCert: {},
|
|
fleet.MDMAssetAPNSKey: {},
|
|
fleet.MDMAssetCACert: {},
|
|
fleet.MDMAssetCAKey: {},
|
|
fleet.MDMAssetSCEPChallenge: {},
|
|
fleet.MDMAssetVPPTokenDeprecated: {},
|
|
}
|
|
)
|
|
|
|
func setupSharedFlags() {
|
|
for _, fs := range []*flag.FlagSet{exportCmd, importCmd} {
|
|
fs.StringVar(&flagKey, "key", "", "Key used to encrypt the assets")
|
|
fs.StringVar(&flagDir, "dir", "", "Directory to put the exported assets")
|
|
fs.StringVar(&flagDBUser, "db-user", testUsername, "Username used to connect to the MySQL instance")
|
|
fs.StringVar(&flagDBPass, "db-password", testPassword, "Password used to connect to the MySQL instance")
|
|
fs.StringVar(&flagDBAddress, "db-address", testAddress, "Address used to connect to the MySQL instance")
|
|
fs.StringVar(&flagDBName, "db-name", testDatabase, "Name of the database with the asset information in the MySQL instance")
|
|
}
|
|
}
|
|
|
|
func setupDS(privateKey, userName, password, address, name string) *mysql.Datastore {
|
|
db, err := sql.Open(
|
|
"mysql",
|
|
fmt.Sprintf("%s:%s@tcp(%s)/?multiStatements=true", testUsername, testPassword, testAddress),
|
|
)
|
|
if err != nil {
|
|
log.Fatal("opening MySQL connection:", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
mysqlCfg := config.MysqlConfig{
|
|
Username: userName,
|
|
Password: password,
|
|
Address: address,
|
|
Database: name,
|
|
}
|
|
ds, err := mysql.New(
|
|
mysqlCfg,
|
|
clock.NewMockClock(),
|
|
mysql.LimitAttempts(1),
|
|
mysql.WithFleetConfig(&config.FleetConfig{
|
|
Server: config.ServerConfig{
|
|
PrivateKey: privateKey,
|
|
},
|
|
}),
|
|
)
|
|
if err != nil {
|
|
log.Fatal("creating datastore instance:", err) //nolint:gocritic // ignore exitAfterDefer
|
|
}
|
|
|
|
return ds
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
log.Fatal("invalid subcommand, expected import or export")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Flag setup
|
|
setupSharedFlags()
|
|
importCmd.StringVar(&flagImportName, "name", "", "Name of the asset to import. Valid names are: apns_cert, apns_key, ca_cert, ca_key, abm_key, abm_cert, abm_token, scep_challenge, vpp_token")
|
|
importCmd.StringVar(&flagImportValue, "value", "", "Value of the asset to import")
|
|
exportCmd.StringVar(&flagExportName, "name", "", "Name of the asset to export. Valid names are: apns_cert, apns_key, ca_cert, ca_key, abm_key, abm_cert, abm_token, scep_challenge, vpp_token")
|
|
|
|
// Execute subcommands
|
|
switch os.Args[1] {
|
|
case "import":
|
|
if err := importCmd.Parse(os.Args[2:]); err != nil {
|
|
log.Fatal("parsing import flags", err)
|
|
}
|
|
|
|
if len(flagKey) > 32 {
|
|
// We truncate to 32 bytes because AES-256 requires a 32 byte (256 bit) PK, but some
|
|
// infra setups generate keys that are longer than 32 bytes.
|
|
flagKey = flagKey[:32]
|
|
}
|
|
|
|
ds := setupDS(flagKey, flagDBUser, flagDBPass, flagDBAddress, flagDBName)
|
|
defer ds.Close()
|
|
|
|
// Check required flags
|
|
if flagDir != "" {
|
|
if err := os.MkdirAll(flagDir, os.ModePerm); err != nil {
|
|
log.Fatal("ensuring directory: ", err) //nolint:gocritic // ignore exitAfterDefer
|
|
}
|
|
}
|
|
|
|
if flagImportName == "" {
|
|
log.Fatal("-name flag is required")
|
|
}
|
|
|
|
if flagImportValue == "" {
|
|
log.Fatal("-value flag is required")
|
|
}
|
|
|
|
if _, ok := validNames[fleet.MDMAssetName(flagImportName)]; !ok {
|
|
log.Fatalf("invalid asset name %s", flagImportName)
|
|
}
|
|
|
|
err := ds.ReplaceMDMConfigAssets(ctx,
|
|
[]fleet.MDMConfigAsset{{Name: fleet.MDMAssetName(flagImportName), Value: []byte(flagImportValue)}}, nil)
|
|
if err != nil {
|
|
log.Fatal("writing asset to db: ", err)
|
|
}
|
|
return
|
|
case "export":
|
|
if err := exportCmd.Parse(os.Args[2:]); err != nil {
|
|
log.Fatal("parsing export flags", err)
|
|
}
|
|
|
|
// Check required flags
|
|
if flagKey == "" {
|
|
log.Fatal("-key flag is required")
|
|
}
|
|
|
|
if len(flagKey) > 32 {
|
|
// We truncate to 32 bytes because AES-256 requires a 32 byte (256 bit) PK, but some
|
|
// infra setups generate keys that are longer than 32 bytes.
|
|
flagKey = flagKey[:32]
|
|
}
|
|
|
|
ds := setupDS(flagKey, flagDBUser, flagDBPass, flagDBAddress, flagDBName)
|
|
defer ds.Close()
|
|
|
|
if flagDir != "" {
|
|
if err := os.MkdirAll(flagDir, os.ModePerm); err != nil {
|
|
log.Fatal("ensuring directory: ", err)
|
|
}
|
|
}
|
|
|
|
names := []fleet.MDMAssetName{
|
|
fleet.MDMAssetCACert,
|
|
fleet.MDMAssetCAKey,
|
|
fleet.MDMAssetAPNSKey,
|
|
fleet.MDMAssetAPNSCert,
|
|
fleet.MDMAssetABMCert,
|
|
fleet.MDMAssetABMKey,
|
|
fleet.MDMAssetABMTokenDeprecated,
|
|
fleet.MDMAssetSCEPChallenge,
|
|
fleet.MDMAssetVPPTokenDeprecated,
|
|
}
|
|
|
|
if flagExportName != "" {
|
|
if _, ok := validNames[fleet.MDMAssetName(flagExportName)]; !ok {
|
|
log.Fatalf("invalid asset name %s", flagExportName)
|
|
}
|
|
|
|
names = []fleet.MDMAssetName{fleet.MDMAssetName(flagExportName)}
|
|
}
|
|
|
|
assets, err := ds.GetAllMDMConfigAssetsByName(ctx, names, nil)
|
|
if err != nil && !errors.Is(err, mysql.ErrPartialResult) {
|
|
log.Fatal("retrieving assets from db:", err)
|
|
}
|
|
|
|
for _, asset := range assets {
|
|
path := filepath.Join(flagDir, string(asset.Name))
|
|
switch {
|
|
case strings.Contains(path, "_key"):
|
|
path += ".key"
|
|
case strings.Contains(path, "_cert"):
|
|
path += ".crt"
|
|
}
|
|
if err := os.WriteFile(path, asset.Value, 0o600); err != nil {
|
|
log.Fatal("writing asset:", err)
|
|
}
|
|
|
|
log.Printf("wrote %s in %s", asset.Name, path)
|
|
}
|
|
|
|
flagDir, err = filepath.Abs(flagDir)
|
|
if err != nil {
|
|
log.Fatalf("abs path: %s", err)
|
|
}
|
|
|
|
fmt.Printf(`You can set the following on your Fleet configuration:
|
|
export FLEET_MDM_APPLE_APNS_CERT=%[1]s/apns_cert.crt
|
|
export FLEET_MDM_APPLE_APNS_KEY=%[1]s/apns_key.key
|
|
export FLEET_MDM_APPLE_SCEP_CERT=%[1]s/ca_cert.crt
|
|
export FLEET_MDM_APPLE_SCEP_KEY=%[1]s/ca_key.key
|
|
export FLEET_MDM_APPLE_SCEP_CHALLENGE=$(cat %[1]s/scep_challenge)
|
|
export FLEET_MDM_APPLE_BM_SERVER_TOKEN=%[1]s/abm_token
|
|
export FLEET_MDM_APPLE_BM_CERT=%[1]s/abm_cert.crt
|
|
export FLEET_MDM_APPLE_BM_KEY=%[1]s/abm_key.key
|
|
`, flagDir)
|
|
default:
|
|
log.Fatalf("invalid subcommand %s, valid subcommands: import, export", os.Args[1])
|
|
}
|
|
}
|