Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LaunchDarkly Token Analyzer #3948

Merged
Merged
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
36e64af
initial commit
kashifkhan0771 Feb 21, 2025
1c92298
initial commit
kashifkhan0771 Feb 24, 2025
f143fa8
initial commit
kashifkhan0771 Feb 25, 2025
b840782
inital commit
kashifkhan0771 Feb 26, 2025
aab2c0a
initial working structure for launchdarkly analyzer
kashifkhan0771 Feb 27, 2025
9806026
added more apis
kashifkhan0771 Mar 4, 2025
cc2ac00
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 4, 2025
8ff00ae
added test cases
kashifkhan0771 Mar 4, 2025
745868e
removed imposter print statement
kashifkhan0771 Mar 4, 2025
6d3b8fa
updated some code
kashifkhan0771 Mar 4, 2025
6902b03
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 5, 2025
746e4fb
removed id from printResources
kashifkhan0771 Mar 5, 2025
8452bdd
added nabeel suggestion and set analysis info
kashifkhan0771 Mar 5, 2025
d445b25
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 5, 2025
25356be
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 6, 2025
4235470
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 6, 2025
143947a
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 7, 2025
8a0561a
resolved ahrav comments
kashifkhan0771 Mar 7, 2025
deccdd9
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 7, 2025
8d6265f
Merge branch 'main' into feat/oss-95-launchdarkly-analyzer
kashifkhan0771 Mar 10, 2025
08cdc2f
resolved ahrav comments
kashifkhan0771 Mar 10, 2025
47cdd0c
implemented ahrav's suggestion 🔥
kashifkhan0771 Mar 10, 2025
1c61fce
resolved linter error
kashifkhan0771 Mar 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
inital commit
  • Loading branch information
kashifkhan0771 committed Feb 26, 2025
commit b840782c7d183e2f7f2baf63192f5bb3be1e288a
83 changes: 20 additions & 63 deletions pkg/analyzer/analyzers/launchdarkly/launchdarkly.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:generate generate_permissions permissions.yaml permissions.go launchdarkly
package launchdarkly

import (
@@ -41,6 +42,7 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
color.Green("[i] Valid LaunchDarkly Token\n")
printUser(info.User)
printPermissionsType(info.User.Token)
printResources(info.Resources)

color.Yellow("\n[!] Expires: Never")
}
@@ -52,8 +54,13 @@ func AnalyzePermissions(cfg *config.Config, token string) (*SecretInfo, error) {

var secretInfo = &SecretInfo{}

// get caller identity
if err := FetchUserInformation(client, token, secretInfo); err != nil {
// capture user information in secretInfo
if err := CaptureUserInformation(client, token, secretInfo); err != nil {
return nil, fmt.Errorf("failed to fetch caller identity: %v", err)
}

// capture resources in secretInfo
if err := CaptureResources(client, token, secretInfo); err != nil {
return nil, fmt.Errorf("failed to fetch caller identity: %v", err)
}

@@ -102,7 +109,7 @@ func printUser(user User) {
}
}

// printPermissionType print type of permission token has
// printPermissionsType print permissions type token has
func printPermissionsType(token Token) {
// print permission type. It can be either admin, writer, reader or has inline policy or any custom roles assigned
permission := ""
@@ -116,67 +123,17 @@ func printPermissionsType(token Token) {
}

color.Green("\n[i] Permission Type: %s", permission)
policesTable := table.NewWriter()
policesTable.SetOutputMirror(os.Stdout)
policesTable.AppendHeader(table.Row{"Resource (* means all)", "Action", "Effect"})
permissions := GetTokenPermissions(token)
for resource, actions := range permissions {
for action, effect := range actions {
if effect == "allow" {
policesTable.AppendRow(table.Row{color.GreenString(resource), color.GreenString(action), color.GreenString(effect)})
} else {
policesTable.AppendRow(table.Row{color.YellowString(resource), color.YellowString(action), color.YellowString(effect)})
}
}
}

policesTable.Render()
}

// GetTokenPermissions returns a mapping of allowed and denied actions with resources and effects.
func GetTokenPermissions(token Token) map[string]map[string]string {
permissions := make(map[string]map[string]string)

// Process Inline Role
for _, policy := range token.InlineRole {
processPolicy(policy, permissions)
}

// Process Custom Roles
for _, role := range token.CustomRoles {
for _, policy := range role.Polices {
processPolicy(policy, permissions)
}
}

return permissions
}

// processPolicy updates the permissions map with policy details
func processPolicy(policy Policy, permissions map[string]map[string]string) {
// Handle allowed actions
for _, resource := range policy.Resources {
if _, exists := permissions[resource]; !exists {
permissions[resource] = make(map[string]string)
}
for _, action := range policy.Actions {
permissions[resource][action] = policy.Effect
}
for _, action := range policy.NotActions {
permissions[resource][action] = policy.Effect
}
}

// Handle denied actions
for _, resource := range policy.NotResources {
if _, exists := permissions[resource]; !exists {
permissions[resource] = make(map[string]string)
}
for _, action := range policy.Actions {
permissions[resource][action] = policy.Effect
}
for _, action := range policy.NotActions {
permissions[resource][action] = policy.Effect
}
func printResources(resources []Resource) {
// print resources
color.Green("\n[i] Resources:")
callerTable := table.NewWriter()
callerTable.SetOutputMirror(os.Stdout)
callerTable.AppendHeader(table.Row{"ID", "Name", "Type"})
for _, resource := range resources {
callerTable.AppendRow(table.Row{color.GreenString(resource.ID), color.GreenString(resource.Name),
color.GreenString(resource.Type)})
}
callerTable.Render()
}
57 changes: 54 additions & 3 deletions pkg/analyzer/analyzers/launchdarkly/models.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,17 @@ package launchdarkly

import "sync"

var (
MetadataKey = "key"

// resource types
applicationType = "Application"
repositoryType = "Repository"
projectType = "Project"
environmentType = "Environment"
experimentType = "Expirement"
)

type SecretInfo struct {
User User
Permissions []string
@@ -42,7 +53,16 @@ type CustomRole struct {
AssignedToTeams int
}

// policy is a set of statements
/*
policy is a set of statements

Jargon:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤣

- Resource: List of resources
- NotResources: Except this list of resources
- Actions: List of actions
- NotActions: Except this list of actions
- Effect: Allowed or Denied
*/
type Policy struct {
Resources []string
NotResources []string
@@ -70,8 +90,8 @@ func (s *SecretInfo) addPermission(perm string) {

// hasPermission checks if a particular permission exist in secret info permissions list.
func (s *SecretInfo) hasPermission(perm string) bool {
s.mu.Lock()
defer s.mu.Unlock()
s.mu.RLock()
defer s.mu.RUnlock()

for _, permission := range s.Permissions {
if permission == perm {
@@ -90,6 +110,37 @@ func (s *SecretInfo) appendResource(resource Resource) {
s.Resources = append(s.Resources, resource)
}

// listResourceByType returns a list of resources matching the given type.
func (s *SecretInfo) listResourceByType(resourceType string) []Resource {
s.mu.RLock()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Do we actually need the mutex? 🤔 I didn’t notice any concurrent operations, but I might have missed something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you missed the requests.go file. Large diffs are not rendered by default.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😢 I knew there had to be a reason. Sorry about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries!

defer s.mu.RUnlock()

resources := make([]Resource, 0, len(s.Resources))
for _, resource := range s.Resources {
if resource.Type == resourceType {
resources = append(resources, resource)
}
}

return resources
}

// getResourceByID returns a copy of the resource matching the given ID, or nil if not found.
func (s *SecretInfo) getResourceByID(id string) *Resource {
s.mu.RLock()
defer s.mu.RUnlock()

for _, resource := range s.Resources {
if resource.ID == id {
// return a copy of the resource to avoid accidently making changes in the actual resource
copyResource := resource
return &copyResource
}
}

return nil
}

// hasCustomRoles check if token has any custom roles assigned
func (t Token) hasCustomRoles() bool {
return len(t.CustomRoles) > 0
81 changes: 81 additions & 0 deletions pkg/analyzer/analyzers/launchdarkly/permissions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Oops, something went wrong.
You are viewing a condensed version of this merge commit. You can view the full changes here.