| 95 | } |
| 96 | |
| 97 | func extractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, directory string, archive []byte) error { |
| 98 | logger.Info(ctx, "unpacking source archive", |
| 99 | slog.F("size_bytes", len(archive)), |
| 100 | ) |
| 101 | |
| 102 | err := fs.MkdirAll(directory, 0o700) |
| 103 | if err != nil { |
| 104 | return xerrors.Errorf("create work directory %q: %w", directory, err) |
| 105 | } |
| 106 | |
| 107 | reader := tar.NewReader(bytes.NewBuffer(archive)) |
| 108 | for { |
| 109 | header, err := reader.Next() |
| 110 | if err != nil { |
| 111 | if xerrors.Is(err, io.EOF) { |
| 112 | break |
| 113 | } |
| 114 | return xerrors.Errorf("read template source archive: %w", err) |
| 115 | } |
| 116 | logger.Debug(context.Background(), "read archive entry", |
| 117 | slog.F("name", header.Name), |
| 118 | slog.F("mod_time", header.ModTime), |
| 119 | slog.F("size", header.Size)) |
| 120 | |
| 121 | // Security: don't untar absolute or relative paths, as this can allow a malicious tar to overwrite |
| 122 | // files outside the workdir. |
| 123 | if !filepath.IsLocal(header.Name) { |
| 124 | return xerrors.Errorf("refusing to extract to non-local path") |
| 125 | } |
| 126 | |
| 127 | // nolint: gosec // Safe to no-lint because the filepath.IsLocal check above. |
| 128 | headerPath := filepath.Join(directory, header.Name) |
| 129 | if !strings.HasPrefix(headerPath, filepath.Clean(directory)) { |
| 130 | return xerrors.New("tar attempts to target relative upper directory") |
| 131 | } |
| 132 | mode := header.FileInfo().Mode() |
| 133 | if mode == 0 { |
| 134 | mode = 0o600 |
| 135 | } |
| 136 | |
| 137 | // Always check for context cancellation before reading the next header. |
| 138 | // This is mainly important for unit tests, since a canceled context means |
| 139 | // the underlying directory is going to be deleted. There still exists |
| 140 | // the small race condition that the context is canceled after this, and |
| 141 | // before the disk write. |
| 142 | if ctx.Err() != nil { |
| 143 | return xerrors.Errorf("context canceled: %w", ctx.Err()) |
| 144 | } |
| 145 | switch header.Typeflag { |
| 146 | case tar.TypeDir: |
| 147 | err = fs.MkdirAll(headerPath, mode) |
| 148 | if err != nil { |
| 149 | return xerrors.Errorf("mkdir %q: %w", headerPath, err) |
| 150 | } |
| 151 | logger.Debug(context.Background(), "extracted directory", |
| 152 | slog.F("path", headerPath), |
| 153 | slog.F("mode", fmt.Sprintf("%O", mode))) |
| 154 | case tar.TypeReg: |