Drew Short
3 years ago
7 changed files with 349 additions and 14 deletions
-
12cmd/check.go
-
3go.mod
-
18go.sum
-
201internal/parser/debian.go
-
52internal/parser/dockerfile/dockerfile.go
-
70internal/parser/parser.go
-
5test/data/old_pinned_dependencies_ubuntu.Dockerfile
@ -0,0 +1,201 @@ |
|||
/* |
|||
Copyright © 2021 Drew Short <warrick@sothr.com> |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
*/ |
|||
|
|||
package parser |
|||
|
|||
import ( |
|||
"bytes" |
|||
"errors" |
|||
"strings" |
|||
|
|||
log "github.com/sirupsen/logrus" |
|||
|
|||
"mvdan.cc/sh/v3/syntax" |
|||
) |
|||
|
|||
// DebianCommandParser the parser responsible for handling Debian based images
|
|||
type DebianCommandParser struct { |
|||
parser *syntax.Parser |
|||
printer *syntax.Printer |
|||
} |
|||
|
|||
// newDebianCommandParser Provision a new instance of DebianCommandParser with required components
|
|||
func newDebianCommandParser() DebianCommandParser { |
|||
return DebianCommandParser{ |
|||
parser: syntax.NewParser(), |
|||
printer: syntax.NewPrinter(), |
|||
} |
|||
} |
|||
|
|||
// shellNodeToString Convert a node to the string representation
|
|||
func (c DebianCommandParser) shellNodeToString(node syntax.Node) string { |
|||
buffer := bytes.NewBufferString("") |
|||
err := c.printer.Print(buffer, node) |
|||
if err != nil { |
|||
log.Panic("Failed to convert node to string") |
|||
} |
|||
return buffer.String() |
|||
} |
|||
|
|||
// isRedirectedToAptSourcesList Checks if the list of redirects contains the apt sources list directory
|
|||
func isRedirectedToAptSourcesList(redirects []*syntax.Redirect) bool { |
|||
if redirects != nil && len(redirects) > 0 { |
|||
for _, redirect := range redirects { |
|||
if strings.Contains(redirect.Word.Lit(), "/etc/apt/sources.list") { |
|||
return true |
|||
} |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
// isAddRepositoryExpression Checks for expressions that add to the repository
|
|||
func isAddRepositoryExpression(command syntax.CallExpr, redirects []*syntax.Redirect) bool { |
|||
// Adding via debian tooling
|
|||
if command.Args[0].Lit() == "add-apt-repository" { |
|||
return true |
|||
// Echoing into a sources file directly
|
|||
} else if command.Args[0].Lit() == "echo" && isRedirectedToAptSourcesList(redirects) { |
|||
return true |
|||
} |
|||
return false |
|||
} |
|||
|
|||
// trimQuotes return a string without surrounding quotes
|
|||
func trimQuotes(value string) string { |
|||
if value[0] == '"' && value[len(value)-1] == '"' { |
|||
return value[1 : len(value)-1] |
|||
} else if value[0] == '\'' && value[len(value)-1] == '\'' { |
|||
return value[1 : len(value)-1] |
|||
} |
|||
return value |
|||
} |
|||
|
|||
// getAddedRepository parse the repository information from the shell commands
|
|||
func (c DebianCommandParser) getAddedRepository(command syntax.CallExpr, redirects []*syntax.Redirect) (string, error) { |
|||
// Adding via debian tooling
|
|||
if command.Args[0].Lit() == "add-apt-repository" { |
|||
return command.Args[len(command.Args)-1].Lit(), nil |
|||
// Echoing into a sources file directly
|
|||
} else if command.Args[0].Lit() == "echo" && isRedirectedToAptSourcesList(redirects) { |
|||
for _, argument := range command.Args { |
|||
var argumentNode syntax.Node = argument |
|||
argumentValue := trimQuotes(c.shellNodeToString(argumentNode)) |
|||
if strings.Contains(argumentValue, "deb") || strings.Contains(argumentValue, "http://") { |
|||
return argumentValue, nil |
|||
} |
|||
} |
|||
} |
|||
return "", errors.New("failed to parse repository") |
|||
} |
|||
|
|||
// GetRepositories Returns a list of Repository entries that were identified
|
|||
func (c DebianCommandParser) GetRepositories(command string) ([]*Repository, error) { |
|||
var repositories []*Repository |
|||
|
|||
reader := strings.NewReader(command) |
|||
parsedScript, err := c.parser.Parse(reader, "") |
|||
if err != nil { |
|||
log.Fatalf("Failed to parse script \"%s\"", command) |
|||
return nil, err |
|||
} |
|||
|
|||
syntax.Walk(parsedScript, func(node syntax.Node) bool { |
|||
switch parsedStatement := node.(type) { |
|||
case *syntax.Stmt: |
|||
switch parsedCommand := parsedStatement.Cmd.(type) { |
|||
case *syntax.CallExpr: |
|||
if isAddRepositoryExpression(*parsedCommand, parsedStatement.Redirs) { |
|||
repositoryInformation, err := c.getAddedRepository(*parsedCommand, parsedStatement.Redirs) |
|||
if err != nil { |
|||
log.Fatalf("Unexpected error while parsing repository from \"%s\": %s", c.shellNodeToString(node), err) |
|||
} |
|||
repositories = append(repositories, &Repository{Information: repositoryInformation}) |
|||
log.Debugf("Found repository \"%s\"", repositoryInformation) |
|||
} |
|||
} |
|||
} |
|||
return true |
|||
}) |
|||
|
|||
return repositories, nil |
|||
} |
|||
|
|||
// isAddRepositoryExpression Checks for expressions that add to the repository
|
|||
func isInstallerExpression(command syntax.CallExpr) bool { |
|||
// Adding via debian tooling
|
|||
commandProgram := command.Args[0].Lit() |
|||
if (commandProgram == "apt-get" || commandProgram == "apt") && len(command.Args) > 1 && command.Args[1].Lit() == "install" { |
|||
return true |
|||
} |
|||
return false |
|||
} |
|||
|
|||
func (c DebianCommandParser) getInstalledPinnedPackages(command syntax.CallExpr) ([]*Package, error) { |
|||
var packages []*Package |
|||
|
|||
// Adding via debian tooling
|
|||
commandProgram := command.Args[0].Lit() |
|||
if (commandProgram == "apt-get" || commandProgram == "apt") && len(command.Args) > 1 && command.Args[1].Lit() == "install" { |
|||
for _, argument := range command.Args { |
|||
var argumentNode syntax.Node = argument |
|||
argumentValue := trimQuotes(c.shellNodeToString(argumentNode)) |
|||
if strings.Contains(argumentValue, "=") { |
|||
pinnedPackageParts := strings.SplitN(argumentValue, "=", 2) |
|||
packages = append(packages, &Package{ |
|||
Name: pinnedPackageParts[0], |
|||
Version: pinnedPackageParts[1], |
|||
}) |
|||
} |
|||
} |
|||
return packages, nil |
|||
} |
|||
return packages, errors.New("failed to parse packages") |
|||
} |
|||
|
|||
// GetPinnedPackages get a list of all pinned packages
|
|||
func (c DebianCommandParser) GetPinnedPackages(command string) ([]*Package, error) { |
|||
var packages []*Package |
|||
|
|||
reader := strings.NewReader(command) |
|||
parsedScript, err := c.parser.Parse(reader, "") |
|||
if err != nil { |
|||
log.Fatalf("Failed to parse script \"%s\"", command) |
|||
return nil, err |
|||
} |
|||
|
|||
syntax.Walk(parsedScript, func(node syntax.Node) bool { |
|||
switch parsedStatement := node.(type) { |
|||
case *syntax.Stmt: |
|||
switch parsedCommand := parsedStatement.Cmd.(type) { |
|||
case *syntax.CallExpr: |
|||
if isInstallerExpression(*parsedCommand) { |
|||
pinnedPackages, err := c.getInstalledPinnedPackages(*parsedCommand) |
|||
if err != nil { |
|||
log.Fatalf("Unexpected error while parsing pinned packages from \"%s\": %s", c.shellNodeToString(node), err) |
|||
} |
|||
for _, pinnedPackage := range pinnedPackages { |
|||
packages = append(packages, pinnedPackage) |
|||
log.Debugf("Found pinned package \"%s=%s\"", pinnedPackage.Name, pinnedPackage.Version) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return true |
|||
}) |
|||
|
|||
return packages, nil |
|||
} |
@ -1,7 +1,8 @@ |
|||
FROM ubuntu:18.04 |
|||
|
|||
RUN apt-get update && apt-get install \ |
|||
RUN echo "deb http://archive.canonical.com/ubuntu bionic partner" > /etc/apt/sources.list.d/partner.list \ |
|||
&& apt-get update && apt-get install \ |
|||
ca-certificates=20210119~18.04.1 \ |
|||
curl=7.58.0-2ubuntu3.15 \ |
|||
&& apt-get clean \ |
|||
&& rm -rf /var/lib/apt/lists/* |
|||
&& rm -rf /var/lib/apt/lists/* \ |
Write
Preview
Loading…
Cancel
Save
Reference in new issue