go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/apps/cnquery/cmd/bundle.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package cmd 5 6 import ( 7 "context" 8 _ "embed" 9 "fmt" 10 "os" 11 "strconv" 12 13 "github.com/rs/zerolog/log" 14 "github.com/spf13/cobra" 15 "github.com/spf13/viper" 16 "go.mondoo.com/cnquery/cli/config" 17 "go.mondoo.com/cnquery/explorer" 18 "go.mondoo.com/cnquery/providers" 19 "go.mondoo.com/cnquery/providers-sdk/v1/upstream" 20 "go.mondoo.com/cnquery/utils/stringx" 21 ) 22 23 func init() { 24 // bundle init 25 packBundlesCmd.AddCommand(queryPackInitCmd) 26 27 // bundle lint 28 packBundlesCmd.AddCommand(queryPackLintCmd) 29 30 // publish 31 queryPackPublishCmd.Flags().String("pack-version", "", "Override the version of each pack in the bundle") 32 packBundlesCmd.AddCommand(queryPackPublishCmd) 33 34 rootCmd.AddCommand(packBundlesCmd) 35 } 36 37 var packBundlesCmd = &cobra.Command{ 38 Use: "bundle", 39 Aliases: []string{"pack"}, 40 Short: "Create, upload, and validate query packs.", 41 } 42 43 //go:embed bundle_querypack-example.mql.yaml 44 var embedQueryPackTemplate []byte 45 46 var queryPackInitCmd = &cobra.Command{ 47 Use: "init [path]", 48 Short: "Create an example query pack that you can use as a starting point. If you don't provide a filename, cnquery uses `example-pack.mql.yaml`.", 49 Args: cobra.MaximumNArgs(1), 50 Run: func(cmd *cobra.Command, args []string) { 51 name := "example-pack.mql.yaml" 52 if len(args) == 1 { 53 name = args[0] 54 } 55 56 _, err := os.Stat(name) 57 if err == nil { 58 log.Fatal().Msgf("Query Pack '%s' already exists", name) 59 } 60 61 err = os.WriteFile(name, embedQueryPackTemplate, 0o640) 62 if err != nil { 63 log.Fatal().Err(err).Msgf("Could not write '%s'", name) 64 } 65 log.Info().Msgf("Example query pack file written to %s", name) 66 }, 67 } 68 69 func validate(queryPackBundle *explorer.Bundle) []string { 70 errors := []string{} 71 72 // check that we have uids for packs and queries 73 for i := range queryPackBundle.Packs { 74 pack := queryPackBundle.Packs[i] 75 packId := strconv.Itoa(i) 76 77 if pack.Uid == "" { 78 errors = append(errors, fmt.Sprintf("pack %s does not define a uid", packId)) 79 } else { 80 packId = pack.Uid 81 } 82 83 if pack.Name == "" { 84 errors = append(errors, fmt.Sprintf("pack %s does not define a name", packId)) 85 } 86 87 for j := range pack.Queries { 88 query := pack.Queries[j] 89 queryId := strconv.Itoa(j) 90 if query.Uid == "" { 91 errors = append(errors, fmt.Sprintf("query %s/%s does not define a uid", packId, queryId)) 92 } else { 93 queryId = query.Uid 94 } 95 96 if query.Title == "" { 97 errors = append(errors, fmt.Sprintf("query %s/%s does not define a name", packId, queryId)) 98 } 99 } 100 } 101 102 // we compile after the checks because it removes the uids and replaces it with mrns 103 schema := providers.DefaultRuntime().Schema() 104 _, err := queryPackBundle.Compile(context.Background(), schema) 105 if err != nil { 106 errors = append(errors, "could not compile the query pack bundle", err.Error()) 107 } 108 109 return errors 110 } 111 112 var queryPackLintCmd = &cobra.Command{ 113 Use: "lint [path]", 114 Aliases: []string{"validate"}, 115 Short: "Apply style formatting to a query pack.", 116 Args: cobra.ExactArgs(1), 117 Run: func(cmd *cobra.Command, args []string) { 118 log.Info().Str("file", args[0]).Msg("validate query pack") 119 queryPackBundle, err := explorer.BundleFromPaths(args[0]) 120 if err != nil { 121 log.Fatal().Err(err).Msg("could not load query pack") 122 } 123 124 errors := validate(queryPackBundle) 125 if len(errors) > 0 { 126 log.Error().Msg("could not validate query pack") 127 for i := range errors { 128 fmt.Fprintf(os.Stderr, stringx.Indent(2, errors[i])) 129 } 130 os.Exit(1) 131 } 132 133 log.Info().Msg("valid query pack") 134 }, 135 } 136 137 var queryPackPublishCmd = &cobra.Command{ 138 Use: "publish [path]", 139 Aliases: []string{"upload"}, 140 Short: "Add a user-owned query pack to the Mondoo Security Registry.", 141 Args: cobra.ExactArgs(1), 142 PreRun: func(cmd *cobra.Command, args []string) { 143 viper.BindPFlag("pack-version", cmd.Flags().Lookup("pack-version")) 144 }, 145 Run: func(cmd *cobra.Command, args []string) { 146 opts, optsErr := config.Read() 147 if optsErr != nil { 148 log.Fatal().Err(optsErr).Msg("could not load configuration") 149 } 150 config.DisplayUsedConfig() 151 152 filename := args[0] 153 log.Info().Str("file", filename).Msg("load query pack bundle") 154 queryPackBundle, err := explorer.BundleFromPaths(filename) 155 if err != nil { 156 log.Fatal().Err(err).Msg("could not load query pack bundle") 157 } 158 159 errors := validate(queryPackBundle) 160 if len(errors) > 0 { 161 log.Error().Msg("could not validate query pack") 162 for i := range errors { 163 fmt.Fprintf(os.Stderr, stringx.Indent(2, errors[i])) 164 } 165 os.Exit(1) 166 } 167 log.Info().Msg("valid query pack") 168 169 // compile manipulates the bundle, therefore we read it again 170 queryPackBundle, err = explorer.BundleFromPaths(filename) 171 if err != nil { 172 log.Fatal().Err(err).Msg("could not load query pack bundle") 173 } 174 175 log.Info().Str("space", opts.SpaceMrn).Msg("add query pack bundle to space") 176 overrideVersionFlag := false 177 overrideVersion := viper.GetString("pack-version") 178 if len(overrideVersion) > 0 { 179 overrideVersionFlag = true 180 } 181 182 serviceAccount := opts.GetServiceCredential() 183 if serviceAccount == nil { 184 log.Fatal().Msg("cnquery has no credentials. Log in with `cnquery login`") 185 } 186 187 certAuth, err := upstream.NewServiceAccountRangerPlugin(serviceAccount) 188 if err != nil { 189 log.Error().Err(err).Msg("could not initialize client authentication") 190 os.Exit(ConfigurationErrorCode) 191 } 192 httpClient, err := opts.GetHttpClient() 193 if err != nil { 194 log.Fatal().Err(err).Msg("error while creating Mondoo API client") 195 } 196 queryHubServices, err := explorer.NewQueryHubClient(opts.UpstreamApiEndpoint(), httpClient, certAuth) 197 if err != nil { 198 log.Fatal().Err(err).Msg("could not connect to the Mondoo Security Registry") 199 } 200 201 // set the owner mrn for spaces 202 queryPackBundle.OwnerMrn = opts.SpaceMrn 203 ctx := context.Background() 204 205 // override version and/or labels 206 for i := range queryPackBundle.Packs { 207 p := queryPackBundle.Packs[i] 208 209 // override query pack version 210 if overrideVersionFlag { 211 p.Version = overrideVersion 212 } 213 } 214 215 // send data upstream 216 _, err = queryHubServices.SetBundle(ctx, queryPackBundle) 217 if err != nil { 218 log.Fatal().Err(err).Msg("could not add query packs") 219 } 220 221 log.Info().Msg("successfully added query packs") 222 }, 223 }