Skip to content

Instantly share code, notes, and snippets.

@vdparikh
Last active February 11, 2025 23:56
Show Gist options
  • Select an option

  • Save vdparikh/d0553586b0651bf293835522dc61a89c to your computer and use it in GitHub Desktop.

Select an option

Save vdparikh/d0553586b0651bf293835522dc61a89c to your computer and use it in GitHub Desktop.

Revisions

  1. vdparikh revised this gist Feb 11, 2025. 2 changed files with 233 additions and 0 deletions.
    File renamed without changes.
    233 changes: 233 additions & 0 deletions password_rotate_slack.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,233 @@
    package main

    import (
    "crypto/rand"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "strings"

    "github.com/go-ldap/ldap/v3"
    "github.com/slack-go/slack"
    )

    // GenerateRandomPassword generates a random password of a given length, avoiding specified characters.
    func GenerateRandomPassword(length int, avoidChars string) (string, error) {
    const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;:,.<>?`~"
    var validChars []rune

    // Filter out characters to avoid
    for _, char := range charset {
    if !strings.ContainsRune(avoidChars, char) {
    validChars = append(validChars, char)
    }
    }

    if len(validChars) == 0 {
    return "", fmt.Errorf("no valid characters available after filtering")
    }

    bytes := make([]byte, length)
    _, err := rand.Read(bytes)
    if err != nil {
    return "", err
    }

    password := make([]rune, length)
    for i := range password {
    password[i] = validChars[int(bytes[i])%len(validChars)]
    }

    return string(password), nil
    }

    // CheckIfUserIsOwner checks if the Slack user is the owner of the service account in Active Directory.
    func CheckIfUserIsOwner(serviceAccount, userEmail string) (bool, error) {
    // Connect to LDAP server
    l, err := ldap.Dial("tcp", "ldap.example.com:389")
    if err != nil {
    return false, err
    }
    defer l.Close()

    // Bind with admin credentials
    err = l.Bind("[email protected]", "adminpassword")
    if err != nil {
    return false, err
    }

    // Search for the service account's managedBy attribute
    searchRequest := ldap.NewSearchRequest(
    fmt.Sprintf("CN=%s,OU=ServiceAccounts,DC=example,DC=com", serviceAccount),
    ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
    "(objectClass=*)",
    []string{"managedBy"},
    nil,
    )

    result, err := l.Search(searchRequest)
    if err != nil {
    return false, err
    }

    if len(result.Entries) == 0 {
    return false, fmt.Errorf("service account not found")
    }

    // Get the managedBy attribute (owner of the service account)
    managedBy := result.Entries[0].GetAttributeValue("managedBy")
    if managedBy == "" {
    return false, fmt.Errorf("managedBy attribute not found for service account")
    }

    // Compare the managedBy value with the Slack user's email
    return managedBy == userEmail, nil
    }

    // UpdateADPassword updates the password in Active Directory.
    func UpdateADPassword(serviceAccount, newPassword string) error {
    // Connect to LDAP server
    l, err := ldap.Dial("tcp", "ldap.example.com:389")
    if err != nil {
    return err
    }
    defer l.Close()

    // Bind with admin credentials
    err = l.Bind("[email protected]", "adminpassword")
    if err != nil {
    return err
    }

    // Prepare the password update request
    modifyRequest := ldap.NewModifyRequest(fmt.Sprintf("CN=%s,OU=ServiceAccounts,DC=example,DC=com", serviceAccount), nil)
    modifyRequest.Replace("unicodePwd", []byte(fmt.Sprintf("\"%s\"", newPassword)))

    // Update the password
    err = l.Modify(modifyRequest)
    if err != nil {
    return err
    }

    log.Printf("Password updated in Active Directory for service account: %s\n", serviceAccount)
    return nil
    }

    // RotatePasswordInE rotates the password in E.
    func RotatePasswordInE(serviceAccount, newPassword string) error {
    // Replace with actual E API calls
    // Example: Call E API to update the password
    log.Printf("Password rotated in E for service account: %s\n", serviceAccount)
    return nil
    }

    // RotatePasswordInVault rotates the password in HashiCorp Vault.
    func RotatePasswordInVault(serviceAccount, newPassword string) error {
    // Initialize Vault client
    config := vault.DefaultConfig()
    config.Address = "http://vault.example.com:8200"

    client, err := vault.NewClient(config)
    if err != nil {
    return err
    }

    // Set the Vault token (replace with your actual token)
    client.SetToken("s.xxxxxxxx")

    // Write the new password to Vault
    secretData := map[string]interface{}{
    "password": newPassword,
    }

    _, err = client.Logical().Write(fmt.Sprintf("secret/data/%s", serviceAccount), secretData)
    if err != nil {
    return err
    }

    log.Printf("Password rotated in HashiCorp Vault for service account: %s\n", serviceAccount)
    return nil
    }

    // SlackHandler handles Slack slash commands.
    func SlackHandler(w http.ResponseWriter, r *http.Request) {
    // Parse the Slack slash command payload
    err := r.ParseForm()
    if err != nil {
    http.Error(w, "Failed to parse form data", http.StatusBadRequest)
    return
    }

    // Extract the Slack user ID and service account name
    slackUserID := r.FormValue("user_id")
    serviceAccount := r.FormValue("text")

    // Initialize Slack client
    slackToken := os.Getenv("SLACK_TOKEN")
    slackClient := slack.New(slackToken)

    // Get the Slack user's email
    userInfo, err := slackClient.GetUserInfo(slackUserID)
    if err != nil {
    http.Error(w, "Failed to get Slack user info", http.StatusInternalServerError)
    return
    }
    userEmail := userInfo.Profile.Email

    // Check if the Slack user is the owner of the service account
    isOwner, err := CheckIfUserIsOwner(serviceAccount, userEmail)
    if err != nil {
    http.Error(w, fmt.Sprintf("Failed to validate ownership: %v", err), http.StatusInternalServerError)
    return
    }

    if !isOwner {
    http.Error(w, "You are not the owner of this service account", http.StatusForbidden)
    return
    }

    // Define characters to avoid (e.g., quotes, special characters)
    avoidChars := `"'`

    // Generate a random password, avoiding specified characters
    newPassword, err := GenerateRandomPassword(16, avoidChars)
    if err != nil {
    http.Error(w, fmt.Sprintf("Failed to generate random password: %v", err), http.StatusInternalServerError)
    return
    }

    // Update password in Active Directory
    err = UpdateADPassword(serviceAccount, newPassword)
    if err != nil {
    http.Error(w, fmt.Sprintf("Failed to update password in Active Directory: %v", err), http.StatusInternalServerError)
    return
    }

    // Rotate password in E
    err = RotatePasswordInE(serviceAccount, newPassword)
    if err != nil {
    http.Error(w, fmt.Sprintf("Failed to rotate password in E: %v", err), http.StatusInternalServerError)
    return
    }

    // Rotate password in HashiCorp Vault
    err = RotatePasswordInVault(serviceAccount, newPassword)
    if err != nil {
    http.Error(w, fmt.Sprintf("Failed to rotate password in HashiCorp Vault: %v", err), http.StatusInternalServerError)
    return
    }

    // Respond to Slack
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Password rotation completed successfully!"))
    }

    func main() {
    // Start the web server to handle Slack requests
    http.HandleFunc("/rotate-password", SlackHandler)
    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
    }
  2. vdparikh created this gist Feb 11, 2025.
    195 changes: 195 additions & 0 deletions password_update.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,195 @@
    package main

    import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "log"
    "os"
    "strings"

    "github.com/go-ldap/ldap/v3"
    vault "github.com/hashicorp/vault/api"
    )

    // GenerateRandomPassword generates a random password of a given length, avoiding specified characters.
    func GenerateRandomPassword(length int, avoidChars string) (string, error) {
    const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;:,.<>?`~"
    var validChars []rune

    // Filter out characters to avoid
    for _, char := range charset {
    if !strings.ContainsRune(avoidChars, char) {
    validChars = append(validChars, char)
    }
    }

    if len(validChars) == 0 {
    return "", fmt.Errorf("no valid characters available after filtering")
    }

    bytes := make([]byte, length)
    _, err := rand.Read(bytes)
    if err != nil {
    return "", err
    }

    password := make([]rune, length)
    for i := range password {
    password[i] = validChars[int(bytes[i])%len(validChars)]
    }

    return string(password), nil
    }

    // CheckIfUserIsOwner checks if the invoking user is the owner of the service account in Active Directory.
    func CheckIfUserIsOwner(serviceAccount, invokingUser string) (bool, error) {
    // Connect to LDAP server
    l, err := ldap.Dial("tcp", "ldap.example.com:389")
    if err != nil {
    return false, err
    }
    defer l.Close()

    // Bind with admin credentials
    err = l.Bind("[email protected]", "adminpassword")
    if err != nil {
    return false, err
    }

    // Search for the service account's managedBy attribute
    searchRequest := ldap.NewSearchRequest(
    fmt.Sprintf("CN=%s,OU=ServiceAccounts,DC=example,DC=com", serviceAccount),
    ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
    "(objectClass=*)",
    []string{"managedBy"},
    nil,
    )

    result, err := l.Search(searchRequest)
    if err != nil {
    return false, err
    }

    if len(result.Entries) == 0 {
    return false, fmt.Errorf("service account not found")
    }

    // Get the managedBy attribute (owner of the service account)
    managedBy := result.Entries[0].GetAttributeValue("managedBy")
    if managedBy == "" {
    return false, fmt.Errorf("managedBy attribute not found for service account")
    }

    // Compare the managedBy value with the invoking user
    return managedBy == invokingUser, nil
    }

    // UpdateADPassword updates the password in Active Directory.
    func UpdateADPassword(serviceAccount, newPassword string) error {
    // Connect to LDAP server
    l, err := ldap.Dial("tcp", "ldap.example.com:389")
    if err != nil {
    return err
    }
    defer l.Close()

    // Bind with admin credentials
    err = l.Bind("[email protected]", "adminpassword")
    if err != nil {
    return err
    }

    // Prepare the password update request
    modifyRequest := ldap.NewModifyRequest(fmt.Sprintf("CN=%s,OU=ServiceAccounts,DC=example,DC=com", serviceAccount), nil)
    modifyRequest.Replace("unicodePwd", []byte(fmt.Sprintf("\"%s\"", newPassword)))

    // Update the password
    err = l.Modify(modifyRequest)
    if err != nil {
    return err
    }

    log.Printf("Password updated in Active Directory for service account: %s\n", serviceAccount)
    return nil
    }

    // RotatePasswordInE rotates the password in E.
    func RotatePasswordInE(serviceAccount, newPassword string) error {
    // Replace with actual E API calls
    // Example: Call E API to update the password
    log.Printf("Password rotated in E for service account: %s\n", serviceAccount)
    return nil
    }

    // RotatePasswordInVault rotates the password in HashiCorp Vault.
    func RotatePasswordInVault(serviceAccount, newPassword string) error {
    // Initialize Vault client
    config := vault.DefaultConfig()
    config.Address = "http://vault.example.com:8200"

    client, err := vault.NewClient(config)
    if err != nil {
    return err
    }

    // Set the Vault token (replace with your actual token)
    client.SetToken("s.xxxxxxxx")

    // Write the new password to Vault
    secretData := map[string]interface{}{
    "password": newPassword,
    }

    _, err = client.Logical().Write(fmt.Sprintf("secret/data/%s", serviceAccount), secretData)
    if err != nil {
    return err
    }

    log.Printf("Password rotated in HashiCorp Vault for service account: %s\n", serviceAccount)
    return nil
    }

    func main() {
    serviceAccount := "svc-account"
    invokingUser := os.Getenv("USER") // Replace with actual invoking user (e.g., from environment or input)

    // Check if the invoking user is the owner of the service account
    isOwner, err := CheckIfUserIsOwner(serviceAccount, invokingUser)
    if err != nil {
    log.Fatalf("Failed to check service account ownership: %v", err)
    }

    if !isOwner {
    log.Fatalf("User %s is not the owner of service account %s", invokingUser, serviceAccount)
    }

    // Define characters to avoid (e.g., quotes, special characters)
    avoidChars := `"'`

    // Generate a random password, avoiding specified characters
    newPassword, err := GenerateRandomPassword(16, avoidChars)
    if err != nil {
    log.Fatalf("Failed to generate random password: %v", err)
    }

    // Update password in Active Directory
    err = UpdateADPassword(serviceAccount, newPassword)
    if err != nil {
    log.Fatalf("Failed to update password in Active Directory: %v", err)
    }

    // Rotate password in E
    err = RotatePasswordInE(serviceAccount, newPassword)
    if err != nil {
    log.Fatalf("Failed to rotate password in E: %v", err)
    }

    // Rotate password in HashiCorp Vault
    err = RotatePasswordInVault(serviceAccount, newPassword)
    if err != nil {
    log.Fatalf("Failed to rotate password in HashiCorp Vault: %v", err)
    }

    log.Println("Password rotation completed successfully!")
    }