Last active
February 11, 2025 23:56
-
-
Save vdparikh/d0553586b0651bf293835522dc61a89c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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