diff --git a/cmd/headscale/headscale.go b/cmd/headscale/headscale.go index ebad844e..fe656a0c 100644 --- a/cmd/headscale/headscale.go +++ b/cmd/headscale/headscale.go @@ -169,6 +169,43 @@ var enableRouteCmd = &cobra.Command{ }, } +var preauthkeysCmd = &cobra.Command{ + Use: "preauthkey", + Short: "Handle the preauthkeys in Headscale", +} + +var listPreAuthKeys = &cobra.Command{ + Use: "list NAMESPACE", + Short: "List the preauthkeys for this namespace", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("Missing parameters") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + h, err := getHeadscaleApp() + if err != nil { + log.Fatalf("Error initializing: %s", err) + } + keys, err := h.GetPreAuthKeys(args[0]) + if err != nil { + fmt.Println(err) + return + } + for _, k := range *keys { + fmt.Printf( + "key: %s, namespace: %s, reusable: %v, expiration: %s, created_at: %s", + k.Key, + k.Namespace.Name, + k.Reusable, + k.Expiration.Format("2006-01-02 15:04:05"), + k.CreatedAt.Format("2006-01-02 15:04:05"), + ) + } + }, +} + func main() { viper.SetConfigName("config") viper.AddConfigPath("/etc/headscale/") @@ -183,14 +220,18 @@ func main() { headscaleCmd.AddCommand(versionCmd) headscaleCmd.AddCommand(serveCmd) headscaleCmd.AddCommand(registerCmd) + headscaleCmd.AddCommand(preauthkeysCmd) headscaleCmd.AddCommand(namespaceCmd) + headscaleCmd.AddCommand(nodeCmd) + namespaceCmd.AddCommand(createNamespaceCmd) namespaceCmd.AddCommand(listNamespacesCmd) - headscaleCmd.AddCommand(nodeCmd) nodeCmd.AddCommand(listRoutesCmd) nodeCmd.AddCommand(enableRouteCmd) + preauthkeysCmd.AddCommand(listPreAuthKeys) + if err := headscaleCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) diff --git a/db.go b/db.go index 7e1e0cc9..a1005568 100644 --- a/db.go +++ b/db.go @@ -24,6 +24,7 @@ func (h *Headscale) initDB() error { db.AutoMigrate(&Machine{}) db.AutoMigrate(&KV{}) db.AutoMigrate(&Namespace{}) + db.AutoMigrate(&PreAuthKey{}) db.Close() h.setValue("db_version", dbVersion) diff --git a/preauth_keys.go b/preauth_keys.go new file mode 100644 index 00000000..14fcdbb8 --- /dev/null +++ b/preauth_keys.go @@ -0,0 +1,78 @@ +package headscale + +import ( + "crypto/rand" + "encoding/hex" + "log" + "time" +) + +type PreAuthKey struct { + ID uint64 `gorm:"primary_key"` + Key string + NamespaceID uint + Namespace Namespace + Reusable bool + + CreatedAt *time.Time + Expiration *time.Time +} + +func (h *Headscale) CreatePreAuthKey(namespaceName string, reusable bool, expiration *time.Time) (*PreAuthKey, error) { + n, err := h.GetNamespace(namespaceName) + if err != nil { + return nil, err + } + + db, err := h.db() + if err != nil { + log.Printf("Cannot open DB: %s", err) + return nil, err + } + defer db.Close() + + now := time.Now().UTC() + kstr, err := h.generateKey() + if err != nil { + return nil, err + } + + k := PreAuthKey{ + Key: kstr, + NamespaceID: n.ID, + Reusable: reusable, + CreatedAt: &now, + Expiration: expiration, + } + db.Save(&k) + + return &k, nil +} + +func (h *Headscale) GetPreAuthKeys(namespaceName string) (*[]PreAuthKey, error) { + n, err := h.GetNamespace(namespaceName) + if err != nil { + return nil, err + } + db, err := h.db() + if err != nil { + log.Printf("Cannot open DB: %s", err) + return nil, err + } + defer db.Close() + + keys := []PreAuthKey{} + if err := db.Where(&PreAuthKey{NamespaceID: n.ID}).Find(&keys).Error; err != nil { + return nil, err + } + return &keys, nil +} + +func (h *Headscale) generateKey() (string, error) { + size := 24 + bytes := make([]byte, size) + if _, err := rand.Read(bytes); err != nil { + return "", err + } + return hex.EncodeToString(bytes), nil +}