github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/cmd/tools/jackal.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package tools contains the CLI commands for Jackal. 5 package tools 6 7 import ( 8 "fmt" 9 "os" 10 11 "slices" 12 13 "github.com/AlecAivazis/survey/v2" 14 "github.com/Racer159/jackal/src/cmd/common" 15 "github.com/Racer159/jackal/src/config" 16 "github.com/Racer159/jackal/src/config/lang" 17 "github.com/Racer159/jackal/src/internal/packager/git" 18 "github.com/Racer159/jackal/src/internal/packager/helm" 19 "github.com/Racer159/jackal/src/pkg/cluster" 20 "github.com/Racer159/jackal/src/pkg/message" 21 "github.com/Racer159/jackal/src/pkg/packager/sources" 22 "github.com/Racer159/jackal/src/pkg/pki" 23 "github.com/Racer159/jackal/src/pkg/zoci" 24 "github.com/Racer159/jackal/src/types" 25 "github.com/defenseunicorns/pkg/helpers" 26 "github.com/defenseunicorns/pkg/oci" 27 "github.com/sigstore/cosign/v2/pkg/cosign" 28 "github.com/spf13/cobra" 29 ) 30 31 var subAltNames []string 32 var outputDirectory string 33 var updateCredsInitOpts types.JackalInitOptions 34 35 var deprecatedGetGitCredsCmd = &cobra.Command{ 36 Use: "get-git-password", 37 Hidden: true, 38 Short: lang.CmdToolsGetGitPasswdShort, 39 Long: lang.CmdToolsGetGitPasswdLong, 40 Run: func(_ *cobra.Command, _ []string) { 41 message.Warn(lang.CmdToolsGetGitPasswdDeprecation) 42 getCredsCmd.Run(getCredsCmd, []string{"git"}) 43 }, 44 } 45 46 var getCredsCmd = &cobra.Command{ 47 Use: "get-creds", 48 Short: lang.CmdToolsGetCredsShort, 49 Long: lang.CmdToolsGetCredsLong, 50 Example: lang.CmdToolsGetCredsExample, 51 Aliases: []string{"gc"}, 52 Args: cobra.MaximumNArgs(1), 53 Run: func(_ *cobra.Command, args []string) { 54 state, err := cluster.NewClusterOrDie().LoadJackalState() 55 if err != nil || state.Distro == "" { 56 // If no distro the jackal secret did not load properly 57 message.Fatalf(nil, lang.ErrLoadState) 58 } 59 60 if len(args) > 0 { 61 // If a component name is provided, only show that component's credentials 62 message.PrintComponentCredential(state, args[0]) 63 } else { 64 message.PrintCredentialTable(state, nil) 65 } 66 }, 67 } 68 69 var updateCredsCmd = &cobra.Command{ 70 Use: "update-creds", 71 Short: lang.CmdToolsUpdateCredsShort, 72 Long: lang.CmdToolsUpdateCredsLong, 73 Example: lang.CmdToolsUpdateCredsExample, 74 Aliases: []string{"uc"}, 75 Args: cobra.MaximumNArgs(1), 76 Run: func(cmd *cobra.Command, args []string) { 77 validKeys := []string{message.RegistryKey, message.GitKey, message.ArtifactKey, message.AgentKey} 78 if len(args) == 0 { 79 args = validKeys 80 } else { 81 if !slices.Contains(validKeys, args[0]) { 82 cmd.Help() 83 message.Fatalf(nil, lang.CmdToolsUpdateCredsInvalidServiceErr, message.RegistryKey, message.GitKey, message.ArtifactKey) 84 } 85 } 86 87 c := cluster.NewClusterOrDie() 88 oldState, err := c.LoadJackalState() 89 if err != nil || oldState.Distro == "" { 90 // If no distro the jackal secret did not load properly 91 message.Fatalf(nil, lang.ErrLoadState) 92 } 93 var newState *types.JackalState 94 if newState, err = c.MergeJackalState(oldState, updateCredsInitOpts, args); err != nil { 95 message.Fatal(err, lang.CmdToolsUpdateCredsUnableUpdateCreds) 96 } 97 98 message.PrintCredentialUpdates(oldState, newState, args) 99 100 confirm := config.CommonOptions.Confirm 101 102 if confirm { 103 message.Note(lang.CmdToolsUpdateCredsConfirmProvided) 104 } else { 105 prompt := &survey.Confirm{ 106 Message: lang.CmdToolsUpdateCredsConfirmContinue, 107 } 108 if err := survey.AskOne(prompt, &confirm); err != nil { 109 message.Fatalf(nil, lang.ErrConfirmCancel, err) 110 } 111 } 112 113 if confirm { 114 // Update registry and git pull secrets 115 if slices.Contains(args, message.RegistryKey) { 116 c.UpdateJackalManagedImageSecrets(newState) 117 } 118 if slices.Contains(args, message.GitKey) { 119 c.UpdateJackalManagedGitSecrets(newState) 120 } 121 122 // Update artifact token (if internal) 123 if slices.Contains(args, message.ArtifactKey) && newState.ArtifactServer.PushToken == "" && newState.ArtifactServer.InternalServer { 124 g := git.New(oldState.GitServer) 125 tokenResponse, err := g.CreatePackageRegistryToken() 126 if err != nil { 127 // Warn if we couldn't actually update the git server (it might not be installed and we should try to continue) 128 message.Warnf(lang.CmdToolsUpdateCredsUnableCreateToken, err.Error()) 129 } else { 130 newState.ArtifactServer.PushToken = tokenResponse.Sha1 131 } 132 } 133 134 // Save the final Jackal State 135 err = c.SaveJackalState(newState) 136 if err != nil { 137 message.Fatalf(err, lang.ErrSaveState) 138 } 139 140 // Update Jackal 'init' component Helm releases if present 141 h := helm.NewClusterOnly(&types.PackagerConfig{State: newState}, c) 142 143 if slices.Contains(args, message.RegistryKey) && newState.RegistryInfo.InternalRegistry { 144 err = h.UpdateJackalRegistryValues() 145 if err != nil { 146 // Warn if we couldn't actually update the registry (it might not be installed and we should try to continue) 147 message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateRegistry, err.Error()) 148 } 149 } 150 if slices.Contains(args, message.GitKey) && newState.GitServer.InternalServer { 151 g := git.New(newState.GitServer) 152 err = g.UpdateJackalGiteaUsers(oldState) 153 if err != nil { 154 // Warn if we couldn't actually update the git server (it might not be installed and we should try to continue) 155 message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateGit, err.Error()) 156 } 157 } 158 if slices.Contains(args, message.AgentKey) { 159 err = h.UpdateJackalAgentValues() 160 if err != nil { 161 // Warn if we couldn't actually update the agent (it might not be installed and we should try to continue) 162 message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateAgent, err.Error()) 163 } 164 } 165 } 166 }, 167 } 168 169 var clearCacheCmd = &cobra.Command{ 170 Use: "clear-cache", 171 Aliases: []string{"c"}, 172 Short: lang.CmdToolsClearCacheShort, 173 Run: func(_ *cobra.Command, _ []string) { 174 message.Notef(lang.CmdToolsClearCacheDir, config.GetAbsCachePath()) 175 if err := os.RemoveAll(config.GetAbsCachePath()); err != nil { 176 message.Fatalf(err, lang.CmdToolsClearCacheErr, config.GetAbsCachePath()) 177 } 178 message.Successf(lang.CmdToolsClearCacheSuccess, config.GetAbsCachePath()) 179 }, 180 } 181 182 var downloadInitCmd = &cobra.Command{ 183 Use: "download-init", 184 Short: lang.CmdToolsDownloadInitShort, 185 Run: func(_ *cobra.Command, _ []string) { 186 url := zoci.GetInitPackageURL(config.CLIVersion) 187 188 remote, err := zoci.NewRemote(url, oci.PlatformForArch(config.GetArch())) 189 if err != nil { 190 message.Fatalf(err, lang.CmdToolsDownloadInitErr, err.Error()) 191 } 192 193 source := &sources.OCISource{Remote: remote} 194 195 _, err = source.Collect(outputDirectory) 196 if err != nil { 197 message.Fatalf(err, lang.CmdToolsDownloadInitErr, err.Error()) 198 } 199 }, 200 } 201 202 var generatePKICmd = &cobra.Command{ 203 Use: "gen-pki HOST", 204 Aliases: []string{"pki"}, 205 Short: lang.CmdToolsGenPkiShort, 206 Args: cobra.ExactArgs(1), 207 Run: func(_ *cobra.Command, args []string) { 208 pki := pki.GeneratePKI(args[0], subAltNames...) 209 if err := os.WriteFile("tls.ca", pki.CA, helpers.ReadAllWriteUser); err != nil { 210 message.Fatalf(err, lang.ErrWritingFile, "tls.ca", err.Error()) 211 } 212 if err := os.WriteFile("tls.crt", pki.Cert, helpers.ReadAllWriteUser); err != nil { 213 message.Fatalf(err, lang.ErrWritingFile, "tls.crt", err.Error()) 214 } 215 if err := os.WriteFile("tls.key", pki.Key, helpers.ReadWriteUser); err != nil { 216 message.Fatalf(err, lang.ErrWritingFile, "tls.key", err.Error()) 217 } 218 message.Successf(lang.CmdToolsGenPkiSuccess, args[0]) 219 }, 220 } 221 222 var generateKeyCmd = &cobra.Command{ 223 Use: "gen-key", 224 Aliases: []string{"key"}, 225 Short: lang.CmdToolsGenKeyShort, 226 Run: func(_ *cobra.Command, _ []string) { 227 // Utility function to prompt the user for the password to the private key 228 passwordFunc := func(bool) ([]byte, error) { 229 // perform the first prompt 230 var password string 231 prompt := &survey.Password{ 232 Message: lang.CmdToolsGenKeyPrompt, 233 } 234 if err := survey.AskOne(prompt, &password); err != nil { 235 return nil, fmt.Errorf(lang.CmdToolsGenKeyErrUnableGetPassword, err.Error()) 236 } 237 238 // perform the second prompt 239 var doubleCheck string 240 rePrompt := &survey.Password{ 241 Message: lang.CmdToolsGenKeyPromptAgain, 242 } 243 if err := survey.AskOne(rePrompt, &doubleCheck); err != nil { 244 return nil, fmt.Errorf(lang.CmdToolsGenKeyErrUnableGetPassword, err.Error()) 245 } 246 247 // check if the passwords match 248 if password != doubleCheck { 249 return nil, fmt.Errorf(lang.CmdToolsGenKeyErrPasswordsNotMatch) 250 } 251 252 return []byte(password), nil 253 } 254 255 // Use cosign to generate the keypair 256 keyBytes, err := cosign.GenerateKeyPair(passwordFunc) 257 if err != nil { 258 message.Fatalf(err, lang.CmdToolsGenKeyErrUnableToGenKeypair, err.Error()) 259 } 260 261 prvKeyFileName := "cosign.key" 262 pubKeyFileName := "cosign.pub" 263 264 // Check if we are about to overwrite existing key files 265 _, prvKeyExistsErr := os.Stat(prvKeyFileName) 266 _, pubKeyExistsErr := os.Stat(pubKeyFileName) 267 if prvKeyExistsErr == nil || pubKeyExistsErr == nil { 268 var confirm bool 269 confirmOverwritePrompt := &survey.Confirm{ 270 Message: fmt.Sprintf(lang.CmdToolsGenKeyPromptExists, prvKeyFileName), 271 } 272 err := survey.AskOne(confirmOverwritePrompt, &confirm) 273 if err != nil { 274 message.Fatalf(err, lang.CmdToolsGenKeyErrNoConfirmOverwrite) 275 } 276 277 if !confirm { 278 message.Fatal(nil, lang.CmdToolsGenKeyErrNoConfirmOverwrite) 279 } 280 } 281 282 // Write the key file contents to disk 283 if err := os.WriteFile(prvKeyFileName, keyBytes.PrivateBytes, helpers.ReadWriteUser); err != nil { 284 message.Fatalf(err, lang.ErrWritingFile, prvKeyFileName, err.Error()) 285 } 286 if err := os.WriteFile(pubKeyFileName, keyBytes.PublicBytes, helpers.ReadAllWriteUser); err != nil { 287 message.Fatalf(err, lang.ErrWritingFile, pubKeyFileName, err.Error()) 288 } 289 290 message.Successf(lang.CmdToolsGenKeySuccess, prvKeyFileName, pubKeyFileName) 291 }, 292 } 293 294 func init() { 295 v := common.InitViper() 296 297 toolsCmd.AddCommand(deprecatedGetGitCredsCmd) 298 toolsCmd.AddCommand(getCredsCmd) 299 300 toolsCmd.AddCommand(updateCredsCmd) 301 302 // Always require confirm flag (no viper) 303 updateCredsCmd.Flags().BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdToolsUpdateCredsConfirmFlag) 304 305 // Flags for using an external Git server 306 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.GitServer.Address, "git-url", v.GetString(common.VInitGitURL), lang.CmdInitFlagGitURL) 307 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.GitServer.PushUsername, "git-push-username", v.GetString(common.VInitGitPushUser), lang.CmdInitFlagGitPushUser) 308 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.GitServer.PushPassword, "git-push-password", v.GetString(common.VInitGitPushPass), lang.CmdInitFlagGitPushPass) 309 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.GitServer.PullUsername, "git-pull-username", v.GetString(common.VInitGitPullUser), lang.CmdInitFlagGitPullUser) 310 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.GitServer.PullPassword, "git-pull-password", v.GetString(common.VInitGitPullPass), lang.CmdInitFlagGitPullPass) 311 312 // Flags for using an external registry 313 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.RegistryInfo.Address, "registry-url", v.GetString(common.VInitRegistryURL), lang.CmdInitFlagRegURL) 314 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.RegistryInfo.PushUsername, "registry-push-username", v.GetString(common.VInitRegistryPushUser), lang.CmdInitFlagRegPushUser) 315 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.RegistryInfo.PushPassword, "registry-push-password", v.GetString(common.VInitRegistryPushPass), lang.CmdInitFlagRegPushPass) 316 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.RegistryInfo.PullUsername, "registry-pull-username", v.GetString(common.VInitRegistryPullUser), lang.CmdInitFlagRegPullUser) 317 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.RegistryInfo.PullPassword, "registry-pull-password", v.GetString(common.VInitRegistryPullPass), lang.CmdInitFlagRegPullPass) 318 319 // Flags for using an external artifact server 320 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.ArtifactServer.Address, "artifact-url", v.GetString(common.VInitArtifactURL), lang.CmdInitFlagArtifactURL) 321 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.ArtifactServer.PushUsername, "artifact-push-username", v.GetString(common.VInitArtifactPushUser), lang.CmdInitFlagArtifactPushUser) 322 updateCredsCmd.Flags().StringVar(&updateCredsInitOpts.ArtifactServer.PushToken, "artifact-push-token", v.GetString(common.VInitArtifactPushToken), lang.CmdInitFlagArtifactPushToken) 323 324 updateCredsCmd.Flags().SortFlags = true 325 326 toolsCmd.AddCommand(clearCacheCmd) 327 clearCacheCmd.Flags().StringVar(&config.CommonOptions.CachePath, "jackal-cache", config.JackalDefaultCachePath, lang.CmdToolsClearCacheFlagCachePath) 328 329 toolsCmd.AddCommand(downloadInitCmd) 330 downloadInitCmd.Flags().StringVarP(&outputDirectory, "output-directory", "o", "", lang.CmdToolsDownloadInitFlagOutputDirectory) 331 332 toolsCmd.AddCommand(generatePKICmd) 333 generatePKICmd.Flags().StringArrayVar(&subAltNames, "sub-alt-name", []string{}, lang.CmdToolsGenPkiFlagAltName) 334 335 toolsCmd.AddCommand(generateKeyCmd) 336 }