Skip to content

Commit e8f1bcc

Browse files
authored
Add gitea secrets subcommand to list Action secrets (#369)
* Implement gitea secrets subcommand
1 parent fcb4bfe commit e8f1bcc

6 files changed

Lines changed: 1415 additions & 0 deletions

File tree

src/pipeleak/cmd/gitea/gitea.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gitea
33
import (
44
"github.com/CompassSecurity/pipeleak/cmd/gitea/enum"
55
"github.com/CompassSecurity/pipeleak/cmd/gitea/scan"
6+
"github.com/CompassSecurity/pipeleak/cmd/gitea/secrets"
67
"github.com/CompassSecurity/pipeleak/cmd/gitea/variables"
78
"github.com/spf13/cobra"
89
)
@@ -17,6 +18,7 @@ func NewGiteaRootCmd() *cobra.Command {
1718

1819
giteaCmd.AddCommand(enum.NewEnumCmd())
1920
giteaCmd.AddCommand(scan.NewScanCmd())
21+
giteaCmd.AddCommand(secrets.NewSecretsCommand())
2022
giteaCmd.AddCommand(variables.NewVariablesCommand())
2123

2224
return giteaCmd
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package secrets
2+
3+
import (
4+
"github.com/CompassSecurity/pipeleak/pkg/gitea/secrets"
5+
"github.com/rs/zerolog/log"
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func NewSecretsCommand() *cobra.Command {
10+
var (
11+
url string
12+
token string
13+
)
14+
15+
cmd := &cobra.Command{
16+
Use: "secrets",
17+
Short: "List all Gitea Actions secrets from groups and repositories",
18+
Long: `Fetches and logs all Actions secrets from organizations and their repositories in Gitea.`,
19+
Run: func(cmd *cobra.Command, args []string) {
20+
config := secrets.Config{
21+
URL: url,
22+
Token: token,
23+
}
24+
25+
if err := secrets.ListAllSecrets(config); err != nil {
26+
log.Fatal().Err(err).Msg("Failed to list secrets")
27+
}
28+
},
29+
}
30+
31+
cmd.Flags().StringVarP(&url, "url", "u", "https://gitea.com", "Gitea server URL")
32+
cmd.Flags().StringVarP(&token, "token", "t", "", "Gitea access token (required)")
33+
34+
_ = cmd.MarkFlagRequired("token")
35+
36+
return cmd
37+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package secrets
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestNewSecretsCommand(t *testing.T) {
10+
cmd := NewSecretsCommand()
11+
12+
assert.NotNil(t, cmd)
13+
assert.Equal(t, "secrets", cmd.Use)
14+
assert.Contains(t, cmd.Short, "Actions secrets")
15+
16+
urlFlag := cmd.Flags().Lookup("url")
17+
assert.NotNil(t, urlFlag)
18+
assert.Equal(t, "https://gitea.com", urlFlag.DefValue)
19+
20+
tokenFlag := cmd.Flags().Lookup("token")
21+
assert.NotNil(t, tokenFlag)
22+
assert.Equal(t, "", tokenFlag.DefValue)
23+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package secrets
2+
3+
import (
4+
"fmt"
5+
6+
"code.gitea.io/sdk/gitea"
7+
"github.com/rs/zerolog/log"
8+
)
9+
10+
type Config struct {
11+
URL string
12+
Token string
13+
}
14+
15+
// clientContext holds both the SDK client and configuration
16+
type clientContext struct {
17+
client *gitea.Client
18+
token string
19+
url string
20+
}
21+
22+
func ListAllSecrets(cfg Config) error {
23+
ctx, err := createClientContext(cfg)
24+
if err != nil {
25+
return fmt.Errorf("failed to create Gitea client: %w", err)
26+
}
27+
28+
// Fetch all repositories user has access to
29+
repos, err := fetchAllRepositories(ctx.client)
30+
if err != nil {
31+
return fmt.Errorf("failed to fetch repositories: %w", err)
32+
}
33+
34+
log.Info().Int("count", len(repos)).Msg("Found repositories")
35+
36+
// Fetch organization secrets
37+
orgs, err := fetchOrganizations(ctx.client)
38+
if err != nil {
39+
return fmt.Errorf("failed to fetch organizations: %w", err)
40+
}
41+
42+
log.Info().Int("count", len(orgs)).Msg("Found organizations")
43+
44+
for _, org := range orgs {
45+
if err := fetchOrgSecrets(ctx.client, org.UserName); err != nil {
46+
log.Warn().Err(err).Str("org", org.UserName).Msg("Failed to fetch organization secrets")
47+
}
48+
}
49+
50+
// Fetch repository secrets for all repos
51+
for _, repo := range repos {
52+
if err := fetchRepoSecrets(ctx.client, repo.Owner.UserName, repo.Name); err != nil {
53+
log.Warn().Err(err).Str("owner", repo.Owner.UserName).Str("repo", repo.Name).Msg("Failed to fetch repository secrets")
54+
}
55+
}
56+
57+
return nil
58+
}
59+
60+
func createClientContext(cfg Config) (*clientContext, error) {
61+
client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token))
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
return &clientContext{
67+
client: client,
68+
token: cfg.Token,
69+
url: cfg.URL,
70+
}, nil
71+
}
72+
73+
func fetchOrganizations(client *gitea.Client) ([]*gitea.Organization, error) {
74+
var allOrgs []*gitea.Organization
75+
page := 1
76+
pageSize := 50
77+
78+
for {
79+
orgs, resp, err := client.ListMyOrgs(gitea.ListOrgsOptions{
80+
ListOptions: gitea.ListOptions{
81+
Page: page,
82+
PageSize: pageSize,
83+
},
84+
})
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
allOrgs = append(allOrgs, orgs...)
90+
91+
if resp == nil || len(orgs) < pageSize {
92+
break
93+
}
94+
page++
95+
}
96+
97+
return allOrgs, nil
98+
}
99+
100+
func fetchAllRepositories(client *gitea.Client) ([]*gitea.Repository, error) {
101+
var allRepos []*gitea.Repository
102+
page := 1
103+
pageSize := 50
104+
105+
for {
106+
repos, resp, err := client.ListMyRepos(gitea.ListReposOptions{
107+
ListOptions: gitea.ListOptions{
108+
Page: page,
109+
PageSize: pageSize,
110+
},
111+
})
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
allRepos = append(allRepos, repos...)
117+
118+
if resp == nil || len(repos) < pageSize {
119+
break
120+
}
121+
page++
122+
}
123+
124+
return allRepos, nil
125+
}
126+
127+
func fetchOrgSecrets(client *gitea.Client, orgName string) error {
128+
page := 1
129+
pageSize := 50
130+
131+
for {
132+
secrets, resp, err := client.ListOrgActionSecret(orgName, gitea.ListOrgActionSecretOption{
133+
ListOptions: gitea.ListOptions{
134+
Page: page,
135+
PageSize: pageSize,
136+
},
137+
})
138+
if err != nil {
139+
return err
140+
}
141+
142+
for _, s := range secrets {
143+
log.Info().
144+
Str("org", orgName).
145+
Str("secret_name", s.Name).
146+
Str("type", "organization").
147+
Msg("Secret")
148+
}
149+
150+
if resp == nil || len(secrets) < pageSize {
151+
break
152+
}
153+
page++
154+
}
155+
156+
return nil
157+
}
158+
159+
func fetchRepoSecrets(client *gitea.Client, owner, repo string) error {
160+
page := 1
161+
pageSize := 50
162+
163+
for {
164+
secrets, resp, err := client.ListRepoActionSecret(owner, repo, gitea.ListRepoActionSecretOption{
165+
ListOptions: gitea.ListOptions{
166+
Page: page,
167+
PageSize: pageSize,
168+
},
169+
})
170+
if err != nil {
171+
return err
172+
}
173+
174+
for _, s := range secrets {
175+
log.Info().
176+
Str("org", owner).
177+
Str("repo", repo).
178+
Str("secret_name", s.Name).
179+
Str("type", "repository").
180+
Msg("Secret")
181+
}
182+
183+
if resp == nil || len(secrets) < pageSize {
184+
break
185+
}
186+
page++
187+
}
188+
189+
return nil
190+
}

0 commit comments

Comments
 (0)