mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Add ssh host command to cli
This commit is contained in:
@@ -17,6 +17,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.foreign("userSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
|
t.foreign("userSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
|
||||||
t.uuid("hostSshCaId").notNullable();
|
t.uuid("hostSshCaId").notNullable();
|
||||||
t.foreign("hostSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
|
t.foreign("hostSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
|
||||||
|
t.unique(["projectId", "hostname"]);
|
||||||
});
|
});
|
||||||
await createOnUpdateTrigger(knex, TableName.SshHost);
|
await createOnUpdateTrigger(knex, TableName.SshHost);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Create SSH Host",
|
description: "Add an SSH Host",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectId: z.string().describe(SSH_HOSTS.CREATE.projectId),
|
projectId: z.string().describe(SSH_HOSTS.CREATE.projectId),
|
||||||
hostname: z
|
hostname: z
|
||||||
@@ -311,7 +311,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Issue SSH credentials (certificate + key)",
|
description: "Issue SSH certificate for user",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.sshHostId)
|
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.sshHostId)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ require (
|
|||||||
github.com/fatih/semgroup v1.2.0
|
github.com/fatih/semgroup v1.2.0
|
||||||
github.com/gitleaks/go-gitdiff v0.8.0
|
github.com/gitleaks/go-gitdiff v0.8.0
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/infisical/go-sdk v0.5.4
|
github.com/infisical/go-sdk v0.5.5
|
||||||
github.com/infisical/infisical-kmip v0.3.5
|
github.com/infisical/infisical-kmip v0.3.5
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
||||||
|
|||||||
@@ -277,8 +277,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/infisical/go-sdk v0.5.4 h1:/Jbl9DLYLmYA3A9W8YB7Kqhm8vymL1WeoITvjXBCq8w=
|
github.com/infisical/go-sdk v0.5.5 h1:A0KfqZvRWScjVj19dbh2uHH4wSsElj5cTAgcT1Adezs=
|
||||||
github.com/infisical/go-sdk v0.5.4/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
github.com/infisical/go-sdk v0.5.5/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
||||||
github.com/infisical/infisical-kmip v0.3.5 h1:QM3s0e18B+mYv3a9HQNjNAlbwZJBzXq5BAJM2scIeiE=
|
github.com/infisical/infisical-kmip v0.3.5 h1:QM3s0e18B+mYv3a9HQNjNAlbwZJBzXq5BAJM2scIeiE=
|
||||||
github.com/infisical/infisical-kmip v0.3.5/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs=
|
github.com/infisical/infisical-kmip v0.3.5/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs=
|
||||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ var sshConnectCmd = &cobra.Command{
|
|||||||
Run: sshConnect,
|
Run: sshConnect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sshAddHostCmd = &cobra.Command{
|
||||||
|
Use: "add-host",
|
||||||
|
Short: "Register a new SSH host with Infisical",
|
||||||
|
Run: sshAddHost,
|
||||||
|
}
|
||||||
|
|
||||||
var algoToFileName = map[infisicalSdkUtil.CertKeyAlgorithm]string{
|
var algoToFileName = map[infisicalSdkUtil.CertKeyAlgorithm]string{
|
||||||
infisicalSdkUtil.RSA2048: "id_rsa_2048",
|
infisicalSdkUtil.RSA2048: "id_rsa_2048",
|
||||||
infisicalSdkUtil.RSA4096: "id_rsa_4096",
|
infisicalSdkUtil.RSA4096: "id_rsa_4096",
|
||||||
@@ -689,7 +695,7 @@ func sshConnect(cmd *cobra.Command, args []string) {
|
|||||||
selectedLoginUser := selectedHost.LoginMappings[loginIdx].LoginUser
|
selectedLoginUser := selectedHost.LoginMappings[loginIdx].LoginUser
|
||||||
|
|
||||||
// Issue SSH creds for host
|
// Issue SSH creds for host
|
||||||
creds, err := infisicalClient.Ssh().IssueCredentialsFromHost(selectedHost.ID, infisicalSdk.IssueSshCredsFromHostOptions{
|
creds, err := infisicalClient.Ssh().IssueSshHostUserCert(selectedHost.ID, infisicalSdk.IssueSshHostUserCertOptions{
|
||||||
LoginUser: selectedLoginUser,
|
LoginUser: selectedLoginUser,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -718,7 +724,101 @@ func sshConnect(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sshAddHost(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
token, err := util.GetInfisicalToken(cmd)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse token")
|
||||||
|
}
|
||||||
|
|
||||||
|
var infisicalToken string
|
||||||
|
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||||
|
infisicalToken = token.Token
|
||||||
|
} else {
|
||||||
|
util.RequireLogin()
|
||||||
|
util.RequireLocalWorkspaceFile()
|
||||||
|
|
||||||
|
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to authenticate")
|
||||||
|
}
|
||||||
|
if loggedInUserDetails.LoginExpired {
|
||||||
|
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login]")
|
||||||
|
}
|
||||||
|
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId, err := cmd.Flags().GetString("projectId")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse --projectId flag")
|
||||||
|
}
|
||||||
|
if projectId == "" {
|
||||||
|
util.PrintErrorMessageAndExit("You must provide --projectId")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, err := cmd.Flags().GetString("hostname")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse --hostname flag")
|
||||||
|
}
|
||||||
|
if hostname == "" {
|
||||||
|
util.PrintErrorMessageAndExit("You must provide --hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
writeUserCaToFile, err := cmd.Flags().GetBool("writeUserCaToFile")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse --writeUserCaToFile flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
userCaOutFilePath, err := cmd.Flags().GetString("userCaOutFilePath")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse --userCaOutFilePath flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
customHeaders, err := util.GetInfisicalCustomHeadersMap()
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to get custom headers")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{
|
||||||
|
SiteUrl: config.INFISICAL_URL,
|
||||||
|
UserAgent: api.USER_AGENT,
|
||||||
|
AutoTokenRefresh: false,
|
||||||
|
CustomHeaders: customHeaders,
|
||||||
|
})
|
||||||
|
client.Auth().SetAccessToken(infisicalToken)
|
||||||
|
|
||||||
|
host, err := client.Ssh().AddSshHost(infisicalSdk.AddSshHostOptions{
|
||||||
|
ProjectID: projectId,
|
||||||
|
Hostname: hostname,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Failed to register SSH host")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✅ Successfully registered host:", host.Hostname)
|
||||||
|
|
||||||
|
publicKey, err := client.Ssh().GetSshHostUserCaPublicKey(host.ID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Failed to fetch associated User CA public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeUserCaToFile {
|
||||||
|
// Expand ~ if used in file path
|
||||||
|
if strings.HasPrefix(userCaOutFilePath, "~") {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to resolve ~ in userCaOutFilePath")
|
||||||
|
}
|
||||||
|
userCaOutFilePath = strings.Replace(userCaOutFilePath, "~", homeDir, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeToFile(userCaOutFilePath, publicKey, 0644); err != nil {
|
||||||
|
util.HandleError(err, "Failed to write User CA public key to file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("📁 Wrote User CA public key to:", userCaOutFilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
func init() {
|
func init() {
|
||||||
sshSignKeyCmd.Flags().String("token", "", "Issue SSH certificate using machine identity access token")
|
sshSignKeyCmd.Flags().String("token", "", "Issue SSH certificate using machine identity access token")
|
||||||
sshSignKeyCmd.Flags().String("certificateTemplateId", "", "The ID of the SSH certificate template to issue the SSH certificate for")
|
sshSignKeyCmd.Flags().String("certificateTemplateId", "", "The ID of the SSH certificate template to issue the SSH certificate for")
|
||||||
@@ -744,6 +844,14 @@ func init() {
|
|||||||
|
|
||||||
sshConnectCmd.Flags().String("token", "", "Use a machine identity access token")
|
sshConnectCmd.Flags().String("token", "", "Use a machine identity access token")
|
||||||
sshCmd.AddCommand(sshConnectCmd)
|
sshCmd.AddCommand(sshConnectCmd)
|
||||||
rootCmd.AddCommand(sshCmd)
|
|
||||||
|
|
||||||
|
sshAddHostCmd.Flags().String("token", "", "Use a machine identity access token")
|
||||||
|
sshAddHostCmd.Flags().String("projectId", "", "Project ID the host belongs to (required)")
|
||||||
|
sshAddHostCmd.Flags().String("hostname", "", "Hostname of the SSH host (required)")
|
||||||
|
sshAddHostCmd.Flags().Bool("writeUserCaToFile", false, "Write User CA public key to /etc/ssh/infisical_user_ca.pub")
|
||||||
|
sshAddHostCmd.Flags().String("userCaOutFilePath", "/etc/ssh/infisical_user_ca.pub", "Custom file path to write the User CA public key")
|
||||||
|
|
||||||
|
sshCmd.AddCommand(sshAddHostCmd)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(sshCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user