Skip to content

Instantly share code, notes, and snippets.

@vdparikh
Last active February 11, 2025 23:56
Show Gist options
  • Save vdparikh/d0553586b0651bf293835522dc61a89c to your computer and use it in GitHub Desktop.
Save vdparikh/d0553586b0651bf293835522dc61a89c to your computer and use it in GitHub Desktop.
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!")
}
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))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment