Skip to content

Commit 110522d

Browse files
authored
Bug 1958790: Store translation table in a secret (#419)
* First draft * Working prototype * Fixes for initial reviews * Adds tests and docs * Replace apply with create/delete * Minor corrections * Adds necessary permissions for gather service account * Small doc fixes
1 parent 1b45229 commit 110522d

File tree

6 files changed

+148
-8
lines changed

6 files changed

+148
-8
lines changed

manifests/03-clusterrole.yaml

+33-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ rules:
179179
verbs:
180180
- get
181181
- list
182-
- watch
182+
- watch
183183
- apiGroups:
184184
- operators.coreos.com
185185
resources:
@@ -214,6 +214,38 @@ subjects:
214214
namespace: openshift-insights
215215
name: gather
216216

217+
---
218+
apiVersion: rbac.authorization.k8s.io/v1
219+
kind: Role
220+
metadata:
221+
name: insights-operator-obfuscation-secret
222+
namespace: openshift-insights
223+
rules:
224+
- apiGroups:
225+
- ""
226+
resources:
227+
- secrets
228+
verbs:
229+
- get
230+
- list
231+
- create
232+
- update
233+
- delete
234+
235+
---
236+
apiVersion: rbac.authorization.k8s.io/v1
237+
kind: RoleBinding
238+
metadata:
239+
name: insights-operator-obfuscation-secret
240+
namespace: openshift-insights
241+
roleRef:
242+
kind: Role
243+
name: insights-operator-obfuscation-secret
244+
subjects:
245+
- kind: ServiceAccount
246+
name: gather
247+
namespace: openshift-insights
248+
217249
---
218250
apiVersion: rbac.authorization.k8s.io/v1
219251
kind: ClusterRoleBinding

pkg/anonymization/anonymizer.go

+58-3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import (
3030
"strings"
3131

3232
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
33+
corev1 "k8s.io/api/core/v1"
3334
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3435
"k8s.io/client-go/kubernetes"
36+
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
3537
"k8s.io/client-go/rest"
3638
"k8s.io/klog/v2"
3739
k8snet "k8s.io/utils/net"
@@ -50,6 +52,13 @@ const (
5052
"some data won't be anonymized(ipv4 and cluster base domain). The error is %v"
5153
)
5254

55+
var (
56+
TranslationTableSecretName = "obfuscation-translation-table" //nolint: gosec
57+
secretAPIVersion = "v1"
58+
secretKind = "Secret"
59+
secretNamespace = "openshift-insights"
60+
)
61+
5362
type subnetInformation struct {
5463
network net.IPNet
5564
lastIP net.IP
@@ -63,14 +72,15 @@ type Anonymizer struct {
6372
networks []subnetInformation
6473
translationTable map[string]string
6574
ipNetworkRegex *regexp.Regexp
75+
secretsClient corev1client.SecretInterface
6676
}
6777

6878
type ConfigProvider interface {
6979
Config() *config.Controller
7080
}
7181

72-
// NewAnonymizer creates a new instance of anonymizer
73-
func NewAnonymizer(clusterBaseDomain string, networks []string) (*Anonymizer, error) {
82+
// NewAnonymizer creates a new instance of anonymizer with a provided config observer and sensitive data
83+
func NewAnonymizer(clusterBaseDomain string, networks []string, secretsClient corev1client.SecretInterface) (*Anonymizer, error) {
7484
networks = append(networks, "127.0.0.1/8")
7585

7686
cidrs, err := k8snet.ParseCIDRs(networks)
@@ -92,6 +102,7 @@ func NewAnonymizer(clusterBaseDomain string, networks []string) (*Anonymizer, er
92102
networks: networksInformation,
93103
translationTable: make(map[string]string),
94104
ipNetworkRegex: regexp.MustCompile(Ipv4AddressOrNetworkRegex),
105+
secretsClient: secretsClient,
95106
}, nil
96107
}
97108

@@ -123,6 +134,8 @@ func NewAnonymizerFromConfigClient(
123134
return nil, err
124135
}
125136

137+
secretsClient := kubeClient.CoreV1().Secrets(secretNamespace)
138+
126139
if installConfig, exists := clusterConfigV1.Data["install-config"]; exists {
127140
networkRegex := regexp.MustCompile(Ipv4NetworkRegex)
128141
networks = append(networks, networkRegex.FindAllString(installConfig, -1)...)
@@ -145,7 +158,7 @@ func NewAnonymizerFromConfigClient(
145158
return network1[0] > network2[0]
146159
})
147160

148-
return NewAnonymizer(baseDomain, networks)
161+
return NewAnonymizer(baseDomain, networks, secretsClient)
149162
}
150163

151164
// NewAnonymizerFromConfig creates a new instance of anonymizer with a provided kubeconfig
@@ -249,6 +262,48 @@ func (anonymizer *Anonymizer) ObfuscateIP(ipStr string) string {
249262
return "::"
250263
}
251264

265+
// Stores the translation table in a Secret in the openshift-insights namespace.
266+
// The actual data is stored in the StringData portion of the Secret.
267+
func (anonymizer *Anonymizer) StoreTranslationTable() *corev1.Secret {
268+
if len(anonymizer.translationTable) == 0 {
269+
return nil
270+
}
271+
defer anonymizer.ResetTranslationTable()
272+
273+
err := anonymizer.secretsClient.Delete(context.TODO(), TranslationTableSecretName, metav1.DeleteOptions{})
274+
if err != nil {
275+
klog.V(4).Infof("Failed to delete translation table secret. err: %s", err)
276+
}
277+
278+
secret := corev1.Secret{
279+
TypeMeta: metav1.TypeMeta{
280+
Kind: secretKind,
281+
APIVersion: secretAPIVersion,
282+
},
283+
ObjectMeta: metav1.ObjectMeta{
284+
Name: TranslationTableSecretName,
285+
},
286+
StringData: anonymizer.translationTable,
287+
}
288+
289+
createOptions := metav1.CreateOptions{
290+
FieldManager: "insights-operator",
291+
}
292+
293+
result, err := anonymizer.secretsClient.Create(context.TODO(), &secret, createOptions)
294+
if err != nil {
295+
klog.Errorf("Failed to create the translation table secret. err: %s", err)
296+
return nil
297+
}
298+
klog.V(3).Infof("Created/Updated %s secret in %s namespace", TranslationTableSecretName, secretNamespace)
299+
return result
300+
}
301+
302+
// Resets the translation table, so that the translation table of multiple gathers wont mix toghater.
303+
func (anonymizer *Anonymizer) ResetTranslationTable() {
304+
anonymizer.translationTable = make(map[string]string)
305+
}
306+
252307
// IsObfuscationEnabled returns true if obfuscation(hiding IP and domain names) is enabled and false otherwise
253308
func IsObfuscationEnabled(configObserver ConfigProvider) bool {
254309
if configObserver == nil {

pkg/anonymization/anonymizer_test.go

+52-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import (
55
"net"
66
"testing"
77

8+
corev1 "k8s.io/api/core/v1"
9+
"k8s.io/apimachinery/pkg/runtime"
10+
811
"github.com/stretchr/testify/assert"
12+
kubefake "k8s.io/client-go/kubernetes/fake"
13+
corefake "k8s.io/client-go/kubernetes/typed/core/v1/fake"
14+
clienttesting "k8s.io/client-go/testing"
915

1016
"github.com/openshift/insights-operator/pkg/record"
1117
)
@@ -107,8 +113,7 @@ func getAnonymizer(t *testing.T) *Anonymizer {
107113
"127.0.0.0/8",
108114
"192.168.0.0/16",
109115
}
110-
111-
anonymizer, err := NewAnonymizer(clusterBaseDomain, networks)
116+
anonymizer, err := NewAnonymizer(clusterBaseDomain, networks, kubefake.NewSimpleClientset().CoreV1().Secrets(secretNamespace))
112117
assert.NoError(t, err)
113118

114119
return anonymizer
@@ -190,4 +195,49 @@ func Test_Anonymizer_TranslationTableTest(t *testing.T) {
190195
}).Data)
191196

192197
assert.Equal(t, "192.168.1.1", obfuscatedData)
198+
199+
assert.Equal(t, 257, len(anonymizer.translationTable))
200+
anonymizer.ResetTranslationTable()
201+
assert.Equal(t, 0, len(anonymizer.translationTable))
202+
}
203+
204+
func Test_Anonymizer_StoreTranslationTable(t *testing.T) {
205+
anonymizer := getAnonymizer(t)
206+
207+
// Empty translation table == No call made to
208+
assert.Nil(t, anonymizer.StoreTranslationTable())
209+
210+
// Mock the client to react/check Apply calls
211+
kube := kubefake.Clientset{}
212+
client := kube.CoreV1().Secrets(secretNamespace)
213+
client.(*corefake.FakeSecrets).Fake.AddReactor("create", "secrets",
214+
func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
215+
if createAction, ok := action.(clienttesting.CreateAction); ok {
216+
assert.Equal(t, secretNamespace, createAction.GetNamespace())
217+
assert.Equal(t, secretAPIVersion, createAction.GetResource().Version)
218+
var secret *corev1.Secret
219+
secret, ok = createAction.GetObject().(*corev1.Secret)
220+
if !ok {
221+
t.Errorf("Failed to convert sent Secret.")
222+
}
223+
return true, secret, nil
224+
}
225+
t.Errorf("Incorrect action, expected patch got %s", action)
226+
return false, nil, nil
227+
})
228+
anonymizer.secretsClient = client
229+
230+
// Fill translation table
231+
for i := 0; i < 10; i++ {
232+
obfuscatedData := string(anonymizer.AnonymizeMemoryRecord(&record.MemoryRecord{
233+
Data: []byte(fmt.Sprintf("192.168.0.%v", 255-i)),
234+
}).Data)
235+
236+
assert.Equal(t, fmt.Sprintf("192.168.0.%v", i+1), obfuscatedData)
237+
}
238+
// Store translation table, then check
239+
secret := anonymizer.StoreTranslationTable()
240+
for i := 0; i < 10; i++ {
241+
assert.Equal(t, secret.StringData[fmt.Sprintf("192.168.0.%v", 255-i)], fmt.Sprintf("192.168.0.%v", i+1))
242+
}
193243
}

pkg/gather/gather_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ func Test_CollectAndRecord(t *testing.T) {
251251
Gather: []string{AllGatherersConst},
252252
EnableGlobalObfuscation: true,
253253
}}
254-
anonymizer, err := anonymization.NewAnonymizer("", nil)
254+
anonymizer, err := anonymization.NewAnonymizer("", nil, nil)
255255
assert.NoError(t, err)
256256

257257
functionReports, err := CollectAndRecordGatherer(context.Background(), gatherer, mockRecorder, mockConfigurator)

pkg/recorder/recorder.go

+3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ func (r *Recorder) Record(rec record.Record) error {
9292

9393
// Flush and save the reports using recorder driver
9494
func (r *Recorder) Flush() error {
95+
if r.anonymizer != nil {
96+
defer r.anonymizer.StoreTranslationTable()
97+
}
9598
records := r.copy()
9699
if len(records) == 0 {
97100
return nil

pkg/recorder/recorder_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func newRecorder(maxArchiveSize int64) Recorder {
6262
driver := driverMock{}
6363
driver.On("Save").Return(nil, nil)
6464

65-
anonymizer, _ := anonymization.NewAnonymizer("", nil)
65+
anonymizer, _ := anonymization.NewAnonymizer("", nil, nil)
6666

6767
interval, _ := time.ParseDuration("1m")
6868
return Recorder{

0 commit comments

Comments
 (0)