Skip to content

Commit 054cd80

Browse files
authored
Init bufpolicy pkg for policies (#3740)
1 parent 47078d3 commit 054cd80

File tree

14 files changed

+1207
-2
lines changed

14 files changed

+1207
-2
lines changed

private/bufpkg/bufplugin/plugin_data_provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var (
2424
NopPluginDataProvider PluginDataProvider = nopPluginDataProvider{}
2525
)
2626

27-
// PluginDataProvider provides PluginsDatas.
27+
// PluginDataProvider provides PluginDatas.
2828
type PluginDataProvider interface {
2929
// GetPluginDatasForPluginKeys gets the PluginDatas for the PluginKeys.
3030
//

private/bufpkg/bufplugin/plugin_key_provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var (
3030

3131
// PluginKeyProvider provides PluginKeys for bufparse.Refs.
3232
type PluginKeyProvider interface {
33-
// GetPluginKeysForPluginRefs gets the PluginKets for the given PluginRefs.
33+
// GetPluginKeysForPluginRefs gets the PluginKeys for the given PluginRefs.
3434
//
3535
// Returned PluginKeys will be in the same order as the input PluginRefs.
3636
//
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package bufpolicy

private/bufpkg/bufpolicy/commit.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package bufpolicy
16+
17+
import (
18+
"sync"
19+
"time"
20+
)
21+
22+
// Commit represents a Commit for a Policy on the BSR.
23+
type Commit interface {
24+
// PolicyKey returns the PolicyKey for the Commit.
25+
PolicyKey() PolicyKey
26+
// CreateTime returns the time the Commit was created on the BSR.
27+
CreateTime() (time.Time, error)
28+
29+
isCommit()
30+
}
31+
32+
// NewCommit returns a new Commit.
33+
func NewCommit(
34+
policyKey PolicyKey,
35+
getCreateTime func() (time.Time, error),
36+
) Commit {
37+
return newCommit(
38+
policyKey,
39+
getCreateTime,
40+
)
41+
}
42+
43+
// *** PRIVATE ***
44+
45+
type commit struct {
46+
policyKey PolicyKey
47+
getCreateTime func() (time.Time, error)
48+
}
49+
50+
func newCommit(
51+
policyKey PolicyKey,
52+
getCreateTime func() (time.Time, error),
53+
) *commit {
54+
return &commit{
55+
policyKey: policyKey,
56+
getCreateTime: sync.OnceValues(getCreateTime),
57+
}
58+
}
59+
60+
func (c *commit) PolicyKey() PolicyKey {
61+
return c.policyKey
62+
}
63+
64+
func (c *commit) CreateTime() (time.Time, error) {
65+
// This may invoke tamper-proofing per newCommit construction.
66+
if _, err := c.policyKey.Digest(); err != nil {
67+
return time.Time{}, err
68+
}
69+
return c.getCreateTime()
70+
}
71+
72+
func (*commit) isCommit() {}

private/bufpkg/bufpolicy/digest.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package bufpolicy
16+
17+
import (
18+
"bytes"
19+
"encoding/hex"
20+
"errors"
21+
"fmt"
22+
"strconv"
23+
"strings"
24+
25+
"github.com/bufbuild/buf/private/bufpkg/bufcas"
26+
"github.com/bufbuild/buf/private/bufpkg/bufparse"
27+
"github.com/bufbuild/buf/private/pkg/syserror"
28+
)
29+
30+
const (
31+
// DigestTypeP1 represents the p1 policy digest type.
32+
//
33+
// The string value of this is "p1".
34+
DigestTypeP1 DigestType = iota + 1
35+
)
36+
37+
var (
38+
// AllDigestTypes are all known DigestTypes.
39+
AllDigestTypes = []DigestType{
40+
DigestTypeP1,
41+
}
42+
digestTypeToString = map[DigestType]string{
43+
DigestTypeP1: "p1",
44+
}
45+
stringToDigestType = map[string]DigestType{
46+
"p1": DigestTypeP1,
47+
}
48+
)
49+
50+
// DigestType is a type of digest.
51+
type DigestType int
52+
53+
// ParseDigestType parses a DigestType from its string representation.
54+
//
55+
// This reverses DigestType.String().
56+
//
57+
// Returns an error of type *bufparse.ParseError if the string could not be parsed.
58+
func ParseDigestType(s string) (DigestType, error) {
59+
d, ok := stringToDigestType[s]
60+
if !ok {
61+
return 0, bufparse.NewParseError(
62+
"policy digest type",
63+
s,
64+
fmt.Errorf("unknown type: %q", s),
65+
)
66+
}
67+
return d, nil
68+
}
69+
70+
// String prints the string representation of the DigestType.
71+
func (d DigestType) String() string {
72+
s, ok := digestTypeToString[d]
73+
if !ok {
74+
return strconv.Itoa(int(d))
75+
}
76+
return s
77+
}
78+
79+
// Digest is a digest of some content.
80+
//
81+
// It consists of a DigestType and a digest value.
82+
type Digest interface {
83+
// String() prints typeString:hexValue.
84+
fmt.Stringer
85+
86+
// Type returns the type of digest.
87+
//
88+
// Always a valid value.
89+
Type() DigestType
90+
// Value returns the digest value.
91+
//
92+
// Always non-empty.
93+
Value() []byte
94+
95+
isDigest()
96+
}
97+
98+
// NewDigest creates a new Digest.
99+
func NewDigest(digestType DigestType, bufcasDigest bufcas.Digest) (Digest, error) {
100+
switch digestType {
101+
case DigestTypeP1:
102+
if bufcasDigest.Type() != bufcas.DigestTypeShake256 {
103+
return nil, syserror.Newf(
104+
"trying to create a %v Digest for a cas Digest of type %v",
105+
digestType,
106+
bufcasDigest.Type(),
107+
)
108+
}
109+
return newDigest(digestType, bufcasDigest), nil
110+
default:
111+
// This is a system error.
112+
return nil, syserror.Newf("unknown DigestType: %v", digestType)
113+
}
114+
}
115+
116+
// ParseDigest parses a Digest from its string representation.
117+
//
118+
// A Digest string is of the form typeString:hexValue.
119+
// The string is expected to be non-empty, If not, an error is returned.
120+
//
121+
// This reverses Digest.String().
122+
//
123+
// Returns an error of type *bufparse.ParseError if the string could not be parsed.
124+
func ParseDigest(s string) (Digest, error) {
125+
if s == "" {
126+
// This should be considered a system error.
127+
return nil, errors.New("empty string passed to ParseDigest")
128+
}
129+
digestTypeString, hexValue, ok := strings.Cut(s, ":")
130+
if !ok {
131+
return nil, bufparse.NewParseError(
132+
"policy digest",
133+
s,
134+
errors.New(`must be in the form "digest_type:digest_hex_value"`),
135+
)
136+
}
137+
digestType, err := ParseDigestType(digestTypeString)
138+
if err != nil {
139+
return nil, bufparse.NewParseError(
140+
"policy digest",
141+
digestTypeString,
142+
err,
143+
)
144+
}
145+
value, err := hex.DecodeString(hexValue)
146+
if err != nil {
147+
return nil, bufparse.NewParseError(
148+
"policy digest",
149+
s,
150+
errors.New(`could not parse hex: must in the form "digest_type:digest_hex_value"`),
151+
)
152+
}
153+
switch digestType {
154+
case DigestTypeP1:
155+
bufcasDigest, err := bufcas.NewDigest(value)
156+
if err != nil {
157+
return nil, err
158+
}
159+
return NewDigest(digestType, bufcasDigest)
160+
default:
161+
return nil, syserror.Newf("unknown DigestType: %v", digestType)
162+
}
163+
}
164+
165+
// DigestEqual returns true if the given Digests are considered equal.
166+
//
167+
// If both Digests are nil, this returns true.
168+
//
169+
// This checks both the DigestType and Digest value.
170+
func DigestEqual(a Digest, b Digest) bool {
171+
if (a == nil) != (b == nil) {
172+
return false
173+
}
174+
if a == nil {
175+
return true
176+
}
177+
if a.Type() != b.Type() {
178+
return false
179+
}
180+
return bytes.Equal(a.Value(), b.Value())
181+
}
182+
183+
/// *** PRIVATE ***
184+
185+
type digest struct {
186+
digestType DigestType
187+
bufcasDigest bufcas.Digest
188+
// Cache as we call String pretty often.
189+
// We could do this lazily but not worth it.
190+
stringValue string
191+
}
192+
193+
// validation should occur outside of this function.
194+
func newDigest(digestType DigestType, bufcasDigest bufcas.Digest) *digest {
195+
return &digest{
196+
digestType: digestType,
197+
bufcasDigest: bufcasDigest,
198+
stringValue: digestType.String() + ":" + hex.EncodeToString(bufcasDigest.Value()),
199+
}
200+
}
201+
202+
func (d *digest) Type() DigestType {
203+
return d.digestType
204+
}
205+
206+
func (d *digest) Value() []byte {
207+
return d.bufcasDigest.Value()
208+
}
209+
210+
func (d *digest) String() string {
211+
return d.stringValue
212+
}
213+
214+
func (*digest) isDigest() {}

private/bufpkg/bufpolicy/errors.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package bufpolicy
16+
17+
import (
18+
"strings"
19+
20+
"github.com/bufbuild/buf/private/bufpkg/bufparse"
21+
"github.com/bufbuild/buf/private/pkg/uuidutil"
22+
"github.com/google/uuid"
23+
)
24+
25+
// DigestMismatchError is the error returned if the Digest of a downloaded Policy
26+
// does not match the expected digest in a buf.lock file.
27+
type DigestMismatchError struct {
28+
FullName bufparse.FullName
29+
CommitID uuid.UUID
30+
ExpectedDigest Digest
31+
ActualDigest Digest
32+
}
33+
34+
// Error implements the error interface.
35+
func (m *DigestMismatchError) Error() string {
36+
if m == nil {
37+
return ""
38+
}
39+
var builder strings.Builder
40+
_, _ = builder.WriteString(`*** Digest verification failed`)
41+
if m.FullName != nil {
42+
_, _ = builder.WriteString(` for "`)
43+
_, _ = builder.WriteString(m.FullName.String())
44+
if m.CommitID != uuid.Nil {
45+
_, _ = builder.WriteString(`:`)
46+
_, _ = builder.WriteString(uuidutil.ToDashless(m.CommitID))
47+
}
48+
_, _ = builder.WriteString(`"`)
49+
}
50+
_, _ = builder.WriteString(` ***`)
51+
_, _ = builder.WriteString("\n")
52+
if m.ExpectedDigest != nil && m.ActualDigest != nil {
53+
_, _ = builder.WriteString("\t")
54+
_, _ = builder.WriteString(`Expected digest (from buf.lock): "`)
55+
_, _ = builder.WriteString(m.ExpectedDigest.String())
56+
_, _ = builder.WriteString(`"`)
57+
_, _ = builder.WriteString("\n")
58+
_, _ = builder.WriteString("\t")
59+
_, _ = builder.WriteString(`Actual digest: "`)
60+
_, _ = builder.WriteString(m.ActualDigest.String())
61+
_, _ = builder.WriteString(`"`)
62+
_, _ = builder.WriteString("\n")
63+
}
64+
_, _ = builder.WriteString("\t")
65+
_, _ = builder.WriteString(`This may be the result of a hand-edited or corrupted buf.lock file, a corrupted local cache, and/or an attack.`)
66+
_, _ = builder.WriteString("\n")
67+
_, _ = builder.WriteString("\t")
68+
_, _ = builder.WriteString(`To clear your local cache, run "buf registry cc".`)
69+
return builder.String()
70+
}

0 commit comments

Comments
 (0)