-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d61982f
commit 4b43059
Showing
3 changed files
with
165 additions
and
1 deletion.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package pin | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/google/go-containerregistry/pkg/name" | ||
"github.com/google/go-containerregistry/pkg/v1/remote" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
var ( | ||
githubImmutableActionArtifactType = "application/vnd.github.actions.package.v1+json" | ||
tagRegex = regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+$`) | ||
) | ||
|
||
type ociManifest struct { | ||
ArtifactType string `json:"artifactType"` | ||
} | ||
|
||
// isImmutableAction checks if the action is an immutable action or not | ||
// It queries the OCI manifest for the action and checks if the artifact type is "application/vnd.github.actions.package.v1+json" | ||
// | ||
// Example usage: | ||
// | ||
// # Immutable action (returns true) | ||
// isImmutableAction("actions/checkout@v4.2.2") | ||
// | ||
// # Non-Immutable action (returns false) | ||
// isImmutableAction("actions/checkout@v4.2.3") | ||
// | ||
// REF - https://github.com/actions/publish-immutable-action/issues/216#issuecomment-2549914784 | ||
func IsImmutableAction(action string) bool { | ||
|
||
artifactType, err := getOCIImageArtifactTypeForGhAction(action) | ||
if err != nil { | ||
// log the error | ||
logrus.WithFields(logrus.Fields{"action": action}).WithError(err).Error("error in getting OCI manifest for image") | ||
return false | ||
} | ||
|
||
if artifactType == githubImmutableActionArtifactType { | ||
return true | ||
} | ||
return false | ||
|
||
} | ||
|
||
// getOCIImageArtifactTypeForGhAction retrieves the artifact type from a GitHub Action's OCI manifest. | ||
// This function is used to determine if an action is immutable by checking its artifact type. | ||
// | ||
// Example usage: | ||
// | ||
// # Immutable action (returns "application/vnd.github.actions.package.v1+json", nil) | ||
// artifactType, err := getOCIImageArtifactTypeForGhAction("actions/checkout@v4.2.2") | ||
// | ||
// Returns: | ||
// - artifactType: The artifact type string from the OCI manifest | ||
// - error: An error if the action format is invalid or if there's a problem retrieving the manifest | ||
func getOCIImageArtifactTypeForGhAction(action string) (string, error) { | ||
|
||
// Split the action into parts (e.g., "actions/checkout@v2" -> ["actions/checkout", "v2"]) | ||
parts := strings.Split(action, "@") | ||
if len(parts) != 2 { | ||
return "", fmt.Errorf("invalid action format") | ||
} | ||
|
||
// convert v1.x.x to 1.x.x which is | ||
// use regexp to match tag version format and replace v in prefix | ||
// as immutable actions image tag is in format 1.x.x (without v prefix) | ||
// REF - https://github.com/actions/publish-immutable-action/issues/216#issuecomment-2549914784 | ||
if tagRegex.MatchString(parts[1]) { | ||
// v1.x.x -> 1.x.x | ||
parts[1] = strings.TrimPrefix(parts[1], "v") | ||
} | ||
|
||
// Convert GitHub action to GHCR image reference using proper OCI reference format | ||
image := fmt.Sprintf("ghcr.io/%s:%s", parts[0], parts[1]) | ||
imageManifest, err := getOCIManifestForImage(image) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
var ociManifest ociManifest | ||
err = json.Unmarshal([]byte(imageManifest), &ociManifest) | ||
if err != nil { | ||
return "", err | ||
} | ||
return ociManifest.ArtifactType, nil | ||
} | ||
|
||
// getOCIManifestForImage retrieves the artifact type from the OCI image manifest | ||
func getOCIManifestForImage(imageRef string) (string, error) { | ||
|
||
// Parse the image reference | ||
ref, err := name.ParseReference(imageRef) | ||
if err != nil { | ||
return "", fmt.Errorf("error parsing reference: %v", err) | ||
} | ||
|
||
// Get the image manifest | ||
desc, err := remote.Get(ref) | ||
if err != nil { | ||
return "", fmt.Errorf("error getting manifest: %v", err) | ||
} | ||
|
||
return string(desc.Manifest), nil | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package pin | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func Test_isImmutableAction(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
action string | ||
want bool | ||
}{ | ||
{ | ||
name: "immutable action - 1", | ||
action: "actions/checkout@v4.2.2", | ||
want: true, | ||
}, | ||
{ | ||
name: "immutable action - 2", | ||
action: "step-security/wait-for-secrets@v1.2.0", | ||
want: true, | ||
}, | ||
{ | ||
name: "non immutable action(valid action)", | ||
action: "sailikhith-stepsecurity/hello-action@v1.0.2", | ||
want: false, | ||
}, | ||
{ | ||
name: "non immutable action(invalid action)", | ||
action: "sailikhith-stepsecurity/no-such-action@v1.0.2", | ||
want: false, | ||
}, | ||
{ | ||
name: " action with release tag doesn't exist", | ||
action: "actions/checkout@1.2.3", | ||
want: false, | ||
}, | ||
{ | ||
name: "invalid action format", | ||
action: "invalid-format", | ||
want: false, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
|
||
got := IsImmutableAction(tt.action) | ||
if got != tt.want { | ||
t.Errorf("isImmutableAction() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
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