Skip to content

Commit 60e080d

Browse files
Implements the frontend logic for gNSI Authz
1 parent 2a302ae commit 60e080d

File tree

11 files changed

+2835
-227
lines changed

11 files changed

+2835
-227
lines changed

Makefile

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ endif
113113

114114
# download and apply patch for gnmi client, which will break advancetls
115115
# backup crypto and gnxi
116-
mkdir backup_crypto
116+
mkdir -p backup_crypto
117117
cp -r vendor/golang.org/x/crypto/* backup_crypto/
118118

119119
# download and patch crypto and gnxi
@@ -126,6 +126,17 @@ endif
126126
git apply patches/0001-Updated-to-filter-and-write-to-file.patch
127127
git apply patches/0003-Fix-client-json-parsing-issue.patch
128128

129+
# Manually adding patched client packages and their dependencies
130+
# to vendor/modules.txt. This satisfies 'go install -mod=vendor' lookup checks,
131+
# which are required after manual patching/copying of gnxi and gnmi-cli code.
132+
echo "github.com/google/gnxi v0.0.0-20181220173256-89f51f0ce1e2" >> vendor/modules.txt
133+
echo "github.com/google/gnxi/gnmi_get" >> vendor/modules.txt
134+
echo "github.com/google/gnxi/gnmi_set" >> vendor/modules.txt
135+
echo "github.com/openconfig/gnmi/cli" >> vendor/modules.txt
136+
echo "github.com/openconfig/gnmi/client/flags" >> vendor/modules.txt
137+
echo "golang.org/x/crypto/ssh/terminal" >> vendor/modules.txt
138+
echo "github.com/openconfig/gnmi/cmd/gnmi_cli" >> vendor/modules.txt
139+
129140
ifeq ($(CROSS_BUILD_ENVIRON),y)
130141
$(GO) build -o ${GOBIN}/gnmi_get -mod=vendor github.com/google/gnxi/gnmi_get
131142
$(GO) build -o ${GOBIN}/gnmi_set -mod=vendor github.com/google/gnxi/gnmi_set

gnmi_server/gnsi_authz.go

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
package gnmi
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
log "github.com/golang/glog"
9+
"github.com/openconfig/gnsi/authz"
10+
"google.golang.org/grpc/codes"
11+
"google.golang.org/grpc/status"
12+
"io"
13+
"os"
14+
"path/filepath"
15+
"strconv"
16+
"sync"
17+
"time"
18+
)
19+
20+
var (
21+
authzMu sync.Mutex
22+
)
23+
24+
const (
25+
authzP4rtTbl string = "AUTHZ_POLICY|p4rt"
26+
authzGnxiTbl string = "AUTHZ_POLICY|gnxi"
27+
authzVersionFld string = "authz_version"
28+
authzCreatedOnFld string = "authz_created_on"
29+
backupExt string = ".bak"
30+
)
31+
32+
type GNSIAuthzServer struct {
33+
*Server
34+
authzMetadata *AuthzMetadata
35+
authzMetadataCopy AuthzMetadata
36+
authz.UnimplementedAuthzServer
37+
}
38+
39+
func (srv *GNSIAuthzServer) Probe(context.Context, *authz.ProbeRequest) (*authz.ProbeResponse, error) {
40+
return nil, status.Errorf(codes.Unimplemented, "method Probe not implemented")
41+
}
42+
func (srv *GNSIAuthzServer) Get(context.Context, *authz.GetRequest) (*authz.GetResponse, error) {
43+
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
44+
}
45+
func NewGNSIAuthzServer(srv *Server) *GNSIAuthzServer {
46+
ret := &GNSIAuthzServer{
47+
Server: srv,
48+
authzMetadata: NewAuthzMetadata(),
49+
}
50+
log.V(2).Infof("gnsi: loading authz metadata from %s", srv.config.AuthzMetaFile)
51+
log.V(2).Infof("gnsi: loading authz policy from %s", srv.config.AuthzPolicyFile)
52+
if err := ret.loadAuthzFreshness(srv.config.AuthzMetaFile); err != nil {
53+
log.V(0).Info(err)
54+
}
55+
ret.authzMetadataCopy = *ret.authzMetadata
56+
ret.writeAuthzMetadataToDB(authzVersionFld, ret.authzMetadata.AuthzVersion)
57+
ret.writeAuthzMetadataToDB(authzCreatedOnFld, ret.authzMetadata.AuthzCreatedOn)
58+
return ret
59+
}
60+
61+
// Rotate implements the gNSI.authz.Rotate RPC.
62+
func (srv *GNSIAuthzServer) Rotate(stream authz.Authz_RotateServer) error {
63+
ctx := stream.Context()
64+
log.Infof("GNSI Authz Rotate RPC")
65+
_, err := authenticate(srv.config, ctx, "gnoi", false)
66+
if err != nil {
67+
log.Errorf("authentication failed in Rotate RPC: %v", err)
68+
return err
69+
}
70+
session := time.Now().Nanosecond()
71+
// Concurrent Authz RPCs are not allowed.
72+
if !authzMu.TryLock() {
73+
log.V(0).Infof("[%v]gNSI: authz.Rotate already in use", session)
74+
return status.Errorf(codes.Aborted, "concurrent authz.Rotate RPCs are not allowed")
75+
}
76+
defer authzMu.Unlock()
77+
78+
log.V(2).Infof("[%v]gNSI: Begin authz.Rotate", session)
79+
defer log.V(2).Infof("[%v]gNSI: End authz.Rotate", session)
80+
81+
srv.checkpointAuthzFreshness()
82+
if err := srv.checkpointAuthzFile(); err != nil {
83+
log.V(0).Infof("Failure during Authz checkpoint: %v", err)
84+
}
85+
for {
86+
req, err := stream.Recv()
87+
if err == io.EOF {
88+
log.V(0).Infof("[%v]gNSI: Received unexpected EOF", session)
89+
// Connection closed without Finalize message. Revert all changes made until now.
90+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
91+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
92+
}
93+
srv.revertAuthzFileFreshness()
94+
return status.Errorf(codes.Aborted, "No Finalize message")
95+
}
96+
if err != nil {
97+
log.V(0).Infof("[%v]gnsi: while processing a rotate request got error: `%v`. Reverting to last good state.", session, err)
98+
// Connection closed without Finalize message. Revert all changes made until now.
99+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
100+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
101+
}
102+
srv.revertAuthzFileFreshness()
103+
return status.Errorf(codes.Aborted, err.Error())
104+
}
105+
if endReq := req.GetFinalizeRotation(); endReq != nil {
106+
// This is the last message. All changes are final.
107+
log.V(2).Infof("[%v]gNSI: Received Finalize: %v", session, endReq)
108+
srv.commitAuthzFileChanges()
109+
srv.saveAuthzFileFreshess(srv.config.AuthzMetaFile)
110+
return nil
111+
}
112+
resp, err := srv.processRotateRequest(req)
113+
if err != nil {
114+
log.V(0).Infof("[%v]gnsi: while processing a rotate request got error: `%v`. Reverting to last good state.", session, err)
115+
// Connection closed without Finalize message. Revert all changes made until now.
116+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
117+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
118+
}
119+
srv.revertAuthzFileFreshness()
120+
return err
121+
}
122+
if err := stream.Send(resp); err != nil {
123+
log.V(0).Infof("[%v]gnsi: while processing a rotate request got error: `%v`. Reverting to last good state.", session, err)
124+
// Connection closed without Finalize message. Revert all changes made until now.
125+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
126+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
127+
}
128+
srv.revertAuthzFileFreshness()
129+
return status.Errorf(codes.Aborted, err.Error())
130+
}
131+
}
132+
}
133+
134+
func (srv *GNSIAuthzServer) processRotateRequest(req *authz.RotateAuthzRequest) (*authz.RotateAuthzResponse, error) {
135+
policyReq := req.GetUploadRequest()
136+
if policyReq == nil {
137+
return nil, status.Errorf(codes.Aborted, `Unknown request: "%v"`, req)
138+
}
139+
log.V(2).Infof("received a gNSI.Authz UploadRequest request message")
140+
log.V(3).Infof("request message: %v", policyReq)
141+
if len(policyReq.GetPolicy()) == 0 {
142+
return nil, status.Errorf(codes.Aborted, "Authz policy cannot be empty!")
143+
}
144+
if len(policyReq.GetVersion()) == 0 {
145+
return nil, status.Errorf(codes.Aborted, "Authz policy version cannot be empty!")
146+
}
147+
if !json.Valid([]byte(policyReq.GetPolicy())) {
148+
return nil, status.Errorf(codes.Aborted, "Authz policy `%v` is malformed", policyReq.GetPolicy())
149+
}
150+
if err := fileCheck(srv.config.AuthzPolicyFile); err != nil {
151+
return nil, status.Errorf(codes.NotFound, "Error in reading file %s: %v. Please try Install.", srv.config.AuthzPolicyFile, err)
152+
}
153+
if srv.gnsiAuthz.authzMetadata.AuthzVersion == policyReq.GetVersion() && !req.GetForceOverwrite() {
154+
return nil, status.Errorf(codes.AlreadyExists, "Authz with version `%v` already exists", policyReq.GetVersion())
155+
}
156+
if err := srv.writeAuthzMetadataToDB(authzVersionFld, policyReq.GetVersion()); err != nil {
157+
return nil, status.Errorf(codes.Aborted, err.Error())
158+
}
159+
if err := srv.writeAuthzMetadataToDB(authzCreatedOnFld, strconv.FormatUint(policyReq.GetCreatedOn(), 10)); err != nil {
160+
return nil, status.Errorf(codes.Aborted, err.Error())
161+
}
162+
if err := srv.saveToAuthzFile(policyReq.GetPolicy()); err != nil {
163+
return nil, status.Errorf(codes.Aborted, err.Error())
164+
}
165+
resp := &authz.RotateAuthzResponse{
166+
RotateResponse: &authz.RotateAuthzResponse_UploadResponse{},
167+
}
168+
return resp, nil
169+
}
170+
171+
func (srv *GNSIAuthzServer) saveToAuthzFile(p string) error {
172+
tmpDst, err := os.CreateTemp(filepath.Dir(srv.config.AuthzPolicyFile), filepath.Base(srv.config.AuthzPolicyFile))
173+
if err != nil {
174+
return err
175+
}
176+
if _, err := tmpDst.Write([]byte(p)); err != nil {
177+
if e := os.Remove(tmpDst.Name()); e != nil {
178+
log.V(1).Infof("Failed to cleanup file: %v: %v", tmpDst.Name(), e)
179+
}
180+
return err
181+
}
182+
if err := tmpDst.Close(); err != nil {
183+
if e := os.Remove(tmpDst.Name()); e != nil {
184+
log.V(1).Infof("Failed to cleanup file: %v: %v", tmpDst.Name(), e)
185+
}
186+
return err
187+
}
188+
if err := os.Rename(tmpDst.Name(), srv.config.AuthzPolicyFile); err != nil {
189+
if e := os.Remove(tmpDst.Name()); e != nil {
190+
log.V(1).Infof("Failed to cleanup file: %v: %v", tmpDst.Name(), e)
191+
}
192+
return err
193+
}
194+
return os.Chmod(srv.config.AuthzPolicyFile, 0600)
195+
}
196+
197+
func (srv *GNSIAuthzServer) checkpointAuthzFile() error {
198+
log.V(2).Infof("Checkpoint authz file: %v", srv.config.AuthzPolicyFile)
199+
return copyFile(srv.config.AuthzPolicyFile, srv.config.AuthzPolicyFile+backupExt)
200+
}
201+
202+
func (srv *GNSIAuthzServer) commitAuthzFileChanges() error {
203+
// Check if the active policy file exists.
204+
srcStat, err := os.Stat(srv.config.AuthzPolicyFile)
205+
if err != nil {
206+
return err
207+
}
208+
if !srcStat.Mode().IsRegular() {
209+
return fmt.Errorf("%s is not a regular file", srv.config.AuthzPolicyFile)
210+
}
211+
// OK. Now the backup can be deleted.
212+
backup := srv.config.AuthzPolicyFile + backupExt
213+
backupStat, err := os.Stat(backup)
214+
if err != nil {
215+
// Already does not exist.
216+
return nil
217+
}
218+
if !backupStat.Mode().IsRegular() {
219+
return fmt.Errorf("%s is not a regular file; did not remove it.", backup)
220+
}
221+
return os.Remove(backup)
222+
}
223+
224+
// writeAuthzMetadataToDB writes the credentials freshness data to the DB.
225+
func (srv *GNSIAuthzServer) writeAuthzMetadataToDB(fld, val string) error {
226+
if err := writeCredentialsMetadataToDB(authzP4rtTbl, "", fld, val); err != nil {
227+
return err
228+
}
229+
if err := writeCredentialsMetadataToDB(authzGnxiTbl, "", fld, val); err != nil {
230+
return err
231+
}
232+
switch fld {
233+
case authzVersionFld:
234+
srv.authzMetadata.AuthzVersion = val
235+
case authzCreatedOnFld:
236+
srv.authzMetadata.AuthzCreatedOn = val
237+
}
238+
return nil
239+
}
240+
241+
type AuthzMetadata struct {
242+
AuthzVersion string `json:"authz_version"`
243+
AuthzCreatedOn string `json:"authz_created_on"`
244+
}
245+
246+
func NewAuthzMetadata() *AuthzMetadata {
247+
return &AuthzMetadata{
248+
AuthzVersion: "unknown",
249+
AuthzCreatedOn: "0",
250+
}
251+
}
252+
253+
func (srv *GNSIAuthzServer) checkpointAuthzFreshness() {
254+
log.V(2).Infof("checkpoint authz freshness")
255+
srv.authzMetadataCopy = *srv.authzMetadata
256+
}
257+
258+
func (srv *GNSIAuthzServer) revertAuthzFileFreshness() {
259+
log.V(2).Infof("revert authz freshness")
260+
srv.writeAuthzMetadataToDB(authzVersionFld, srv.authzMetadataCopy.AuthzVersion)
261+
srv.writeAuthzMetadataToDB(authzCreatedOnFld, srv.authzMetadataCopy.AuthzCreatedOn)
262+
}
263+
264+
func (srv *GNSIAuthzServer) saveAuthzFileFreshess(path string) error {
265+
log.V(2).Infof("save authz metadata to file: %v", path)
266+
buf := new(bytes.Buffer)
267+
enc := json.NewEncoder(buf)
268+
if err := enc.Encode(*srv.authzMetadata); err != nil {
269+
log.V(0).Info(err)
270+
return err
271+
}
272+
if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil {
273+
if e := os.Remove(path); e != nil {
274+
err = fmt.Errorf("Write %s failed: %w; Cleanup failed", path, err)
275+
}
276+
return err
277+
}
278+
return nil
279+
}
280+
281+
func (srv *GNSIAuthzServer) loadAuthzFreshness(path string) error {
282+
log.V(2).Infof("load authz metadata from file: %v", path)
283+
bytes, err := os.ReadFile(path)
284+
if err != nil {
285+
return err
286+
}
287+
return json.Unmarshal(bytes, srv.authzMetadata)
288+
}

0 commit comments

Comments
 (0)