Skip to content

Commit 6f85472

Browse files
kanchanavelusamyniranjanivivek
authored andcommitted
Implements the frontend logic for gNSI Authz
Signed-off-by: kanchanavelusamy <velusamyk@google.com>
1 parent d297045 commit 6f85472

File tree

11 files changed

+1445
-21
lines changed

11 files changed

+1445
-21
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ $(ENVFILE):
222222
check_gotest: $(DBCONFG) $(ENVFILE)
223223
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test -race -coverprofile=coverage-telemetry.txt -covermode=atomic -mod=vendor -v github.com/sonic-net/sonic-gnmi/telemetry
224224
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test -race -coverprofile=coverage-config.txt -covermode=atomic -v github.com/sonic-net/sonic-gnmi/sonic_db_config
225-
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -race -timeout 20m -coverprofile=coverage-gnmi.txt -covermode=atomic -mod=vendor $(BLD_FLAGS) -v github.com/sonic-net/sonic-gnmi/gnmi_server -coverpkg ../...
225+
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -race -timeout 20m -coverprofile=coverage-gnmi.txt -covermode=atomic -mod=vendor $(BLD_FLAGS) -gcflags=all=-l -v github.com/sonic-net/sonic-gnmi/gnmi_server -coverpkg ../...
226226
ifneq ($(ENABLE_DIALOUT_VALUE),0)
227227
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -coverprofile=coverage-dialout.txt -covermode=atomic -mod=vendor $(BLD_FLAGS) -v github.com/sonic-net/sonic-gnmi/dialout/dialout_client
228228
endif

gnmi_server/gnsi_authz.go

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

0 commit comments

Comments
 (0)